P1-B: BufferHandle memory-model ABI + user-facing Tensor cutover - #1453
Draft
YunjiQin wants to merge 10 commits into
Draft
P1-B: BufferHandle memory-model ABI + user-facing Tensor cutover#1453YunjiQin wants to merge 10 commits into
YunjiQin wants to merge 10 commits into
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
YunjiQin
force-pushed
the
p1-b-buffer-handle-abi
branch
from
July 27, 2026 03:31
ec3f15f to
d82c15b
Compare
YunjiQin
force-pushed
the
p1-b-buffer-handle-abi
branch
from
July 27, 2026 06:18
d82c15b to
41cb93e
Compare
6 tasks
YunjiQin
force-pushed
the
p1-b-buffer-handle-abi
branch
3 times, most recently
from
July 27, 2026 12:40
3e6cde6 to
cc65ce6
Compare
The typed, self-describing BufferHandle / BufferRef ABI that replaces the raw-pointer + child_memory mechanism: wire types (child_memory→address_space), versioned blob codec (write/read_bufferref_blob), owner-side BufferHandle + create_host_shared_buffer + ImportRegistry (lazy map-once by canonical identity), self-describing refs (embedded descriptor, no eager handshake), the BufferRef view algebra (slice/transpose/permute/view/reshape), FORK_SHM COW-inherited base VA, Worker.create_buffer, and MAILBOX_SIZE 32768→40960 for the 272 B refs wire. Python buffer_handle.py mirrors buffer_handle.h byte-for-byte (sizes pinned to the binding). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…evice/remote/strided Flip TaskArgs Tensor→BufferRef end-to-end through the C++ dispatch; infer_deps keys a local ref by canonical identity. Three-way consumer split (only the chip leaf rebuilds a C++ Tensor): chip → materialize → run_from_blob; Python sub-worker → MappedArg (torch.frombuffer); nested L4→L3 → re-export (no map on the forwarding hop, canonical identity invariant across the edge — frozen model §5/§8: an L4-owned backing forwarded L4→L3→L2 keeps ONE identity at all three layers, so deps key on identity not VA and aliases/retain-release do not split). Device-memory path (DEVICE_MALLOC materialize/wrap), REMOTE_SIDECAR backend-rewrite at add_ref, and strided-ref → strided-Tensor materialization. Proven L3→L2 + L4→L3→sub in sim and onboard a2a3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
YunjiQin
force-pushed
the
p1-b-buffer-handle-abi
branch
from
July 30, 2026 09:50
4aa2ab6 to
2b2e49d
Compare
… L2 in-process materialization C-phase resource APIs (Worker.alloc_shared_tensor, Orchestrator.alloc_child_tensor); docs/ buffer-handle-abi.md (identity invariant across every edge; re-export preserves it); migrate the C++ GoogleTest orchestrator/scheduler/remote/child_memory tests to the BufferRef ABI; remove TensorTaskArgs (fold onto ChipStorageTaskArgs); restore device-pointer provenance over the BufferRef wire; relax create_buffer to L2 and materialize BufferRef args in-process on an L2 run (uniform user API at every level). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Worker owns and manages the device-memory lifecycle (malloc / free / copy_to / copy_from / alloc_child_tensor, keyed by owner_worker_id); the Orchestrator's memory APIs become thin forwards. The raw-pointer orch.malloc / orch.device_handle are removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… scene tests to BufferRef/handle Worker.make_ref_arg + simpler_setup.make_tensor_ref name a pre-fork host tensor as a memoized FORK_SHM ref; alloc_shared_tensor becomes the alloc→BufferRef managed intermediate; comm-domain windows are exposed as VMM_WINDOW BufferHandles (domain.buffers[name].ref). Migrate the L2 examples, task_timing st, child_memory, and the scene-test L3 rehost path / L3 group + dynamic_register scene tests / post-fork host-buffer ST onto create_buffer + BufferRef args; delete the dead _rewrite_blob_host_addrs; sync docs/comm-domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alars; orch.alloc→handle Complete the Tensor removal from the user-facing path: - ChipStorageTaskArgs is chip-only (ChipWorker._run_slot); Worker.run at L2 takes a BufferRef TaskArgs or None (legacy passthrough removed); migrate the remaining wire-TaskArgs pyut + remote materialization + test_callable_identity + scene-rehost UT fakes off add_tensor. - Delete the create_host_buffer / MAP_HOST subsystem (superseded by create_buffer + lazy ref). - handle.ref(dtype=) accepts a DataType enum; Python sub callables receive scalars (MappedArgs). - orch.alloc returns a BufferHandle over a single identity-keyed Orchestrator::alloc; finish Tensor removal from the L3-L2 message queue / region. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two O(1) warm-path static checks per the frozen model (worker-memory-model §1.4/§6, bufferhandle-abi §3/§4.1), no ABI/wire change: - Capability matrix: BufferHandleDescriptor.__post_init__ rejects an unsupported address_space×backend_kind (the four illegal combos HOST×VMM_WINDOW / HOST×DEVICE_MALLOC / DEVICE×FORK_SHM / DEVICE×POSIX_SHM). Runs on both owner construction and wire decode, so a bad combo fails before dispatch and cannot ride the wire. REMOTE_SIDECAR rows stay allowed here (its P1 blanket reject is ImportRegistry.materialize's job). - access⊆granted: TaskArgs.add_ref (C++ binding) rejects an arg whose TensorArgType needs access the handle does not grant (INPUT→READ, OUTPUT_EXISTING→WRITE, INOUT→READWRITE). Catches e.g. a READ-only FORK_SHM COW buffer tagged OUTPUT_EXISTING (a forked child's writes would silently not reach the parent). Not implemented here (already covered / P2, per the task doc): device→owner-chip topology (_child_prov_check_dispatch), overlapping-alias (infer_deps serializes by identity), unknown abi/backend/REMOTE_SIDECAR reject (already), stale-generation/ABA/visibility (P2). Tests: test_descriptor_rejects_bad_capability_combo / accepts_legal_combos; TestAddRefAccessSubset. Verified a2a3sim async_notify golden 0 (guards don't misfire on legit flows). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Name the Python task-argument type `Tensor` so data is named the same way at every level. The chip-side POD keeps its own name, `ChipTensor`, and stays internal: users write `handle.tensor(shapes, dtype)` and `args.add_tensor(t, tag)`, and the wire type name does not appear in user code at all. The view algebra leaves the Python surface — it had no in-repo caller and no precedent on the C++ binding — while the wire keeps explicit strides so a singleton dimension is never normalized away. Slim the wire ABI while changing it is still cheap: - `CanonicalIdentity` 96 -> 32 B, fixed-length with no length field, so hashing and comparison stay in bounds for any bytes that arrive. The owning worker path leaves the identity and becomes a diagnostic `owner_worker_path_id` whose side table lives only in the owning process; it takes part in no routing, visibility or identity decision. - `owner_instance_id` 16 -> 8 B, still a full-width random draw, since it is now the sole source of cross-incarnation uniqueness. - Drop `descriptor_version` and `visibility`, turn `abi_version` into a `magic` sentinel, and shrink `body` 96 -> 32 B. `BufferRef` is 144 B. - `generation` starts at 1 and 0 is rejected on decode. Padding is excluded from equality and hashing, so two decodes of one backing always key alike instead of splitting into two buckets. Validate every wire element through one shared `validate_buffer_ref` at the three boundaries that accept untrusted bytes: the builder, blob decode, and materialization. It bounds `body_len` and `ndims`, checks the address_space x backend matrix, requires strides > 0 and a known dtype, and rejects a view that extends past its backing. Refuse a DEVICE-space tensor bound for a Python sub-worker at submit instead of letting the child dereference a device address as a host pointer, with a second check at materialization for any later submit path. Stage control-plane copies through a POSIX shm rather than sending a host address across the fork. A parent address is only meaningful in the child when its allocation predates the fork, which callers cannot be required to guarantee; the child_memory example now allocates its H2D source after init() to cover that case. A host BufferHandle transfers by name with no intermediate copy. copy_to/copy_from also validate the handle they are given: address space, backend reachability, access direction and length. Also fixes get_element_size reading past its table for an out-of-range dtype — reachable from the wire, and inherited by the new validation.
Check two things where the argument values are final, in Orchestrator's single submit path so no entry point can skip them. `access ⊆ granted` is re-checked at submit rather than trusted from add time, because a tag stays mutable after its element is added — the pair that governs the dispatch is the one present now, not the one that was checked. Two arguments of one task that name intersecting bytes of the same backing are rejected. Nothing downstream can catch that pair: they belong to one node, so there is no order between them to express, and a host backing staged to the device becomes two independent copies that do not alias for the L2 overlap map to notice. Disjoint slices of one buffer stay legal, and group members are not compared against each other — naming one buffer as every member's output is how a group publishes a completion token for a downstream task. Split the fork-inherited backend in two. FORK_SHM and FORK_COW resolve identically, but a child's write to a MAP_SHARED page reaches the owner while a write to a copy-on-write page splits it into a private copy the owner never sees. Making that a classification rather than something inferred from `access` lets the decoder enforce it: FORK_COW grants READ only. `make_ref_arg` now measures which one it has instead of granting READWRITE unconditionally — a plain tensor is copy-on-write only where a fork stands between the writer and this process, so an L2 leaf, which consumes its args in-process, still gets a writable grant. Also fixes make_ref_arg's handle memo returning a stale entry when the allocator reuses a storage address at a different size. The memo is keyed by address so that every view of one storage shares an identity; a hit whose size no longer matches is a different backing and now gets a fresh identity, instead of lending its nbytes to views that overrun it and fusing two unrelated buffers into one dependency node.
The Python surface names its task-argument type `Tensor`, so the method that builds one from a host tensor should say so too — `make_ref_arg` was the last place the wire type's old name reached user code, and examples call it directly. `simpler_setup.torch_interop.make_tensor_arg` keeps its name: it lives in a different namespace and builds the chip POD for the direct ChipWorker path. The two now sit in one file, so each states at its definition and at its use which side of materialization it produces — a `ChipTensor` carrying an address, or a `Tensor` carrying an identity.
YunjiQin
force-pushed
the
p1-b-buffer-handle-abi
branch
from
July 30, 2026 10:05
2b2e49d to
5ecb644
Compare
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.
P1-B: BufferHandle memory-model ABI + user-facing
TensorcutoverReplaces the legacy "raw pointer +
child_memorybool" L3+ memory mechanism with a typed,self-describing BufferHandle ABI, and flips the live L4→L3 / L3→L2 / L3→SUB dispatch wire from
address-carrying tensor blobs to identity-carrying ones.
The user-facing argument type stays named
Tensorat every level —handle.tensor(shapes, dtype),args.add_tensor(t, tag). What changed underneath it is what aTensorcarries: a canonicalidentity plus a
BufferHandleDescriptor, instead of a raw address and anis_childflag.BufferRefis the C++ wire struct's name and does not appear in user code; the chip-side POD keeps its own name,
ChipTensor, and stays internal to the L2 runtime.User guide:
docs/buffer-handle-abi.md— including the"Why
TensorandChipTensorare two types" section, which is the design question a reviewer is mostlikely to raise (field-by-field, with the two rejected unifications and why).
Commits (read in order)
(
child_memory→address_space), versioned blob codec, ownerBufferHandle+ImportRegistry(lazy map-once by canonical identity), self-describing refs, FORK_SHM COW base VA,
Worker.create_buffer.TaskArgscarries the new wire element through the C++ dispatch;infer_depskeys on canonicalidentity; chip → materialize →
run_from_blob, sub →MappedArg(torch.frombuffer), L4→L3 →re-export; DEVICE_MALLOC + REMOTE_SIDECAR + strided materialization.
alloc_shared_tensor/alloc_child_tensor; migrate the C++ GoogleTest suite; foldTensorTaskArgsonto
ChipStorageTaskArgs; uniform L2 in-process materialization.malloc/free/copy_to/copy_from/alloc_child_tensorlive on the Worker (keyed byowner_worker_id);orch.*are thin forwards; theraw-pointer
orch.malloc/orch.device_handleare removed.a pre-fork host tensor;
allocate_domainhands backVMM_WINDOWBufferHandles(
domain.buffers[name].ref); migrate every collective / demo / L2-L3 example and the scene-testframework.
ChipStorageTaskArgsis the chip-only POD (ChipWorker._run_slot);Worker.runat L2 takes aTaskArgs/None; delete thecreate_host_buffer/MAP_HOSTsubsystem (superseded bycreate_buffer+ lazy import); sub callables receive scalars;orch.allocreturns aBufferHandle.address_space × backend_kindon both owner construction and wire decode; reject an argument whoseTensorArgTypeneeds access the handle does not grant.CanonicalIdentity96→32 B,BufferRef272→144 B; the owner worker path leaves the identity and becomes a diagnosticowner_worker_path_idthat takes part in no routing decision; one sharedvalidate_buffer_refatthe three boundaries that accept untrusted bytes (builder, blob decode, materialization); a
DEVICE-space tensor bound for a Python sub-worker is refused at submit; control-plane copies stage
through a POSIX shm instead of sending a host address across the fork (a host
BufferHandletransfers by name with no intermediate copy). Also fixes an out-of-bounds table read in
get_element_sizereachable from the wire.access ⊆ grantedwherethe values are final (a tag stays mutable after
add_tensor), and reject two arguments of one taskthat name intersecting bytes of one backing — nothing downstream can catch that pair. Split the
fork-inherited backend into
FORK_SHM/FORK_COWso the decoder can enforce that acopy-on-write page grants READ only. Also fixes a stale handle-memo hit when the allocator reuses a
storage address at a different size.
Worker.make_ref_arg→make_tensor_arg— the last place the wire type's old namereached user code.
Verification
a2a3sim: 43 scene tests — allreduce collective ST corpus + L2/L3 examples (allreduce,domain_rank_map, dual_domain_overlap, ffn_tp_parallel, async/deferred notify, vector_add,
create_buffer, child_memory, l3_dependency/group, dynamic_register) — golden 0.
a2a3(Ascend910): L3→L2 chip dispatch, mixed host+device, L4→L3→sub re-export,sdma_async_completion.
Two bugs the new validators caught in existing code, both true positives: a scene-test fixture holding
a 16×int32 view at offset 4 in a 64-byte backing, and a C++
make_ref()that never sethandle.nbytes.Rebased onto current
main. Three of the landed changes interact with this branch and are folded intothe commits that own them, not bolted on at the tip: the launch-acceptance fence's extra
run_from_blobarguments now ride the materialized blob; interior
copy_to/copy_fromranges keep theirallocation-extent check after the ops moved onto the
Worker, so aBufferHandle's recorded extent iswhat admits a partial update; and the depth-two pipeline-slot tests build
Tensorargs forWorker.runwhile keeping the runtime.so-ABI POD for the direct-lease path.Remaining (follow-ups, not in this PR)
release_buffer, in-flight retain / deferred free, generation bump on slotreuse, and dropping an import mapping with its handle.
RemoteTaskArgsWire) still carries chip-tensor metadata over thewire; full remote e2e. REMOTE_SIDECAR materialization stays reserved for P2.
alloc_shared_tensoris the interim runtime-managedintermediate; the dedicated arena is a follow-up.
infer_depsdeliberately keeps the same behavior astoday's L3 +
TensorMapmainline; tightening it is out of scope here.