Compose-time buffer binding: argument-free fused kernels + DeviceBuffer.from_ptr - #8
Open
johnnynunez wants to merge 1 commit into
Conversation
Read/write descriptors optionally take a concrete buffer at construction (TensorRead(x), TensorWrite(out); also TensorSplit/SplitWrite/TensorTSplit dest and TensorPack source). Binding is VALUE-level, like params[]: it never touches token()/codegen — only which pointer the launch uses. A chain with a bound read knows its dtype/shape at compose() time, so FusedKernel compiles EAGERLY there and runs argument-free: k(), k(stream=s). Bound buffers are call defaults — k(y) / k(y, out=z) override them, validated against the compiled signature (ValueError on dtype/shape mismatch; a bound kernel is one signature by design). Read-only binding auto-allocates the output per call; write-only binding keeps the lazy pipe(x) path with the bound buffer as default out=. compose_divergent rejects bindings explicitly (its batch arrives at call time). Unbound chains are untouched (generate_cu output verified byte-identical vs upstream/main). DeviceBuffer.from_ptr(ptr, shape, dtype, stream=None, device=0) wraps an external raw device pointer NON-OWNING (C-style integrations): __del__ and the DLPack deleter never free it — the caller keeps ownership/lifetime. The optional producer stream is advertised via __cuda_array_interface__ v3. Guards on a raw-pointer API: NULL/None ptr raises ValueError; passing a vector dtype AND the trailing channel dim (shape (H, W, 3) + uint8x3, which would silently size the wrapper 3x the allocation) raises TypeError; a producer stream handle of 0 is advertised as 1 (CAI v3 spells the legacy default stream as 1 — the raw 0 is disallowed by the spec). tests/test_bound_compose.py: 41 checks (argless call, eager compile + cache-hit second compose, overrides, from_ptr round-trip incl. raw-pointer output, from_ptr guards, error cases) — 41/41 on GB10 (sm_121, CUDA 13.0) on BOTH backends (clang, nvcc). Full core suite re-run on both backends: all green except the pre-existing Saturate failures (also fail on pristine upstream/main on this machine, unrelated). torch additions in test_torch_integration.py skip cleanly without torch. New example: examples/12_bound_pipeline.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Input/output buffers can now be bound directly on the read/write ops at
compose()time, producing a fused kernel that is fully wired and callable with no arguments:For C-style integrations, raw device pointers enter the same way through a new non-owning wrapper:
Why
compose()time, so compilation happens eagerly there instead of on the first call — the kernel is ready-to-run before the hot loop starts (disk-cache hit when the signature was ever compiled before).ValueErrors on dtype/shape mismatch, instead of silently compiling additional variants.Design
TensorRead(x),TensorWrite(out); alsoTensorSplit/SplitWrite/TensorTSplitdest andTensorPack(ch, source)), matching howemit_read()/emit_write()already make IO the IOp's own concern. No newcompose()kwargs.params[]: it never appears intoken(), the signature, or the generated C++ — bound and unbound composes of the same chain share one cached.so(generate_cuoutput is byte-identical tomainfor all chains).k(y)/k(y, out=z)override them (validated); read-only binding auto-allocates the output per call; write-only binding keeps the lazypipe(x)path with the bound buffer as defaultout=. Unbound chains behave exactly as before.DeviceBuffer.from_ptr(ptr, shape, dtype, stream=None, device=0)is NON-OWNING:__del__and the DLPack deleter never free external memory (new_ownsguard); the caller keeps the allocation alive for as long as the wrapper is in use (documented). The optional producerstreamis advertised through__cuda_array_interface__v3.from_ptris a raw-pointer API, malformed inputs fail loudly at the call site instead of corrupting memory later: a NULL/Nonepointer raisesValueError; passing a vector dtype AND the trailing channel dim ((H, W, 3)+"uint8x3", which would silently size the wrapper at 3x the real allocation) raisesTypeErrorwith the two valid spellings; a producer stream handle of 0 is advertised asstream: 1per CAI v3 (the spec's spelling of the legacy default stream — raw 0 is disallowed), so the synchronization contract is preserved rather than dropped.target="cpu"rejects bindings), lists (batch HF) cannot be bound, andcompose_divergentrejects bindings — all with explicit errors.Tests
tests/test_bound_compose.py(dependency-free harness, 41 checks): argless call incl. in-place input refill,k(stream=s)on a driver-API stream, eager compile + cached second compose (same.so), read-only/write-only bindings, overrides,from_ptrround-trips (wrapping another buffer's pointer as input AND as raw output, vector dtype spec, owner memory surviving wrapper deletion),from_ptrguards (null pointer, ambiguous vector-dtype + trailing-dim spec, CAI v3 stream spelling for 0/None/explicit handles), and 10 error cases (dtype/shape override mismatch, out= mismatch, bound-out mismatch at compose, argless without binding, batch override, cpu target, list binding, divergent binding) plus unbound-path regression checks.tests/test_torch_integration.py(existing skip-if-no-torch pattern): eager compile, argless call, argless on an external torch stream, override, mismatch error.Saturatefailure (fails identically on pristinemainon this machine, unrelated to this change).Docs
README section + status table row,
examples/12_bound_pipeline.py(+ index entry), and updates to thefkl-python-usage/fkl-python-testingskills.🤖 Generated with Claude Code