Add: TMR kernel resident arguments and invocation isolation(K5) - #2189
Draft
Leaf-Salix wants to merge 6 commits into
Draft
Add: TMR kernel resident arguments and invocation isolation(K5)#2189Leaf-Salix wants to merge 6 commits into
Leaf-Salix wants to merge 6 commits into
Conversation
Kernel mode is simpler's second execution identity: instead of owning
the device, a context borrows the caller's already-current device and
stream to enqueue one bounded asynchronous operator per launch, so a
PyPTO program is capturable by ACLGraph as an ordinary node. This
change freezes the public surface that identity hangs off and gives the
context a write-once identity the guards can key on. It creates no
resources. The program path gains one call — simpler_init latches
PROGRAM — and no behavior: latching a fresh context always succeeds, is
idempotent, and nothing on that path reads the latch.
- runtime_c_api.h declares the lifecycle entries
simpler_kernel_mode_{supported,init,prepare_callable,launch} and adds
the host-band code PTO_RUNTIME_ERR_INVALID_STATE. The existing
finalize_device stays the fifth lifecycle entry, and a kernel context
now reaches it. Kernel-mode capacity is
a mode invariant rather than a gated state: config is context-static,
so each pooled arena region is committed at most once, and
setup_static_arena reports a grow or release request on a committed
region under kernel mode as an internal invariant break; capacity
intent travels in CallConfig.runtime_env like everywhere else.
- Execution identity is a write-once property of the context rather
than a state that evolves. ExecutionModeLatch (platform/include/host/
execution_mode_latch.h) replaces the four-state claim: the first init
entry to run latches the mode, and it never changes — not on finalize,
not on error. simpler_init latches PROGRAM before touching any
process or runner state, so the program/kernel mutual exclusion is
enforced on every program init instead of resting on a separate
declaration call. There is no unlatch, which the latch documents as a
consequence: a handle from a failed kernel init can never be recycled
into a program context. SimplerExecutionMode now has one definition
(task_interface/execution_mode.h) that both the wire header and the
latch consume, so the host-side identity and the value that travels to
the AICPU can no longer disagree.
- device_id_ records which device a context is on, not a claim on it —
ownership is what the latch carries. attach_current_thread splits
accordingly: bind_current_thread does the per-thread rtSetDevice and
nothing else; attach_current_thread is the program-mode adopt
(bind plus the one-shot op-execute watchdog and identity write) and
refuses on a kernel latch; adopt_borrowed_device records the device a
kernel context runs on without binding the thread and without
configure_aicore_op_timeout, whose aclrtSetOpExecuteTimeOutV2 would
rewrite the watchdog for every other user of a borrowed card. It does
resolve the timeout config, because the stream and scheduler timeouts
derived from it are read on both identities. DeviceRunner::finalize()
is the one caller that runs under both identities and skips the bind
on a kernel latch, so the kernel close path reaches its no-reset
branch instead of being turned away by a device bind it never needed.
- ensure_acl_ready(), force_reset_device(), and finalize()'s rt-layer
device reset refuse on a kernel-mode context (a2a3 + a5): the ACL
lifecycle belongs to the caller, and every call site of the five ACL
lifecycle APIs falls into three enumerable classes (below the
ensure_acl_ready guard, inside force_reset_device behind its own
guard, or gated on acl_ready_ which only the guarded path sets), with
finalize's rt-layer reset intercepted by its own kernel-mode branch —
so poison recovery can never reset the device out from under the
host process.
- kernel_invocation_header.h pins the envelope every kernel launch
ships to the AICPU (mode / callable / generation / payload length /
int32_t arg counts). Both sides of the wire come from one
build_runtimes.py build, so the struct carries no version or size
negotiation and the POD/standard-layout guards are its only
compile-time checks. generation is the occupancy counter of the
residency slot callable_id resolves to - a property of the slot, not
of the callable in it, so a generation carried by the callable could
not detect slot reuse - with zero reserved for "not recorded".
ChipCallable's sig_count includes the scalar entries and its
scalar_count reads 0 both for a scalar-free orchestration and for an
artifact built before the field existed, so a consumer derives the
effective scalar count - the field when nonzero, otherwise the
signature's SCALAR entries, the split count_callable_tensor_args
already computes - and compares tensor_count against sig_count minus
it. Subtracting the field directly would count an unrecorded
callable's scalars as tensors.
- Kernel-entry argument validation is shared by all eight host-runtime
components through kernel_entry_validation.h (one copy of the
null/range/image-size/alignment checks; a binary pointer and its size
must be present or absent together, and a callable image must be
aligned for ChipCallable so its CALLABLE_CHILD_ALIGN-relative storage_
lands aligned too), so a stub and a real implementation accept and
reject exactly the same arguments.
- KernelExecutionState and ExecutionModeClaimState carry the kernel
context phase machine (New/Collecting/ReadyEnqueued/Poisoned/
Closing/Closed with sticky, retriable Closing and separate
runtime-error and teardown-error slots) and the two restricted
operation vocabularies; synchronize, allocation, capture queries,
and model attachment stay unrepresentable in those tables, and a
launch implementation is obligated to route through them. Every
kernel-mode guard reads the identity through ExecutionModeLatch::
is_kernel() rather than comparing an enumerator at the call site, so
the test lives in one place instead of eight.
- ChipWorker dlsyms the four new symbols from every runtime, so a
component missing one fails at load, and clears them alongside the
other resolved pointers on all three teardown paths so none is left
dangling into the library DlHandleGuard dlcloses.
test_host_runtime_abi.py
asserts the export across all eight components, and table-driven UTs
cover the phase machine (including failed-rollback landing in
Closing with the create error reported and the cleanup error
latched), the shared argument validation, and the wire layout.
- ChipCallable additionally records scalar_count as a cached
derivation of the signature's SCALAR entries: make_callable rejects
a nonzero count that disagrees with the signature, while 0 also
means "not recorded" (legacy blobs read 0). The field occupies four
bytes of historical header tail padding, so every historical offset,
sizeof, and the kernel-cache ABI token are unchanged;
ChipCallable.build gains a trailing scalar_count=0 keyword and a
read-only property.
Two facts a reader should not have to re-derive. The latch refusal returns
PTO_RUNTIME_ERR_INVALID_STATE (-1003) rather than PTO_RUNTIME_ERR_INTERNAL
(-1000) on purpose: conftest.py scrapes "simpler_init failed with code <N>"
and treats -1000 as a poisoned card, so an identity conflict must not look
like one. And kernel_execution_state.cpp stays compiled into all four host
runtimes even though grepping KernelExecutionState now finds only its own
header and .cpp — it is the persistent-state change's foundation, not an
orphaned translation unit.
Every kernel-mode branch this adds is provably dead in this commit: no
production site latches KERNEL (`git grep 'latch(SIMPLER_MODE_KERNEL)' src`
is empty) because both simpler_kernel_mode_init stubs return before any latch
call, so is_kernel() is false on every context and the program path takes the
same branch it took before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Compute TMR kernel arena requirements from the existing per-architecture sizing and reserve-only layout without acquiring device resources. Keep candidates call-local and publish output only on success. Share mode-aware contract and topology validation while keeping the TMR resource set explicit. Preserve valid program behavior and reject unserviceable stream declarations before context creation. Define execution modes in a neutral header shared with the unchanged invocation envelope. Connect the internal builder to validating kernel init stubs; HBG remains unsupported and kernel execution stays disabled. Cover sizing bounds, packed input, independent concurrent calls, real loader admission, sim entry behavior, and C/C++ header compatibility. Relevant tests and sim partitions pass. Full CTest retains the existing profiler quiesce failure (139/140); onboard validation remains pending.
Use the upstream execution-mode header as the single definition. Keep resource contracts independent of kernel lifecycle and invocation headers, and verify that boundary through compiler dependencies. Leave invocation layout ownership and its original wire tests with K1; remove the resource-contract test that additionally pins wire offsets. TMR sizing, resource validation, and init preflight remain unchanged.
Separate common invocation admission from the TMR wire codec. Encode independently owned packets with transactional host templates; consume trusted callable and execution-binding views without owning runtime resources or changing program-mode execution. Add bounded decoding, caller-owned argument conversion, unit tests, and an isolated CPU transport snapshot probe. Production kernel launch remains disabled until resource providers and execution are integrated.
Integrate persistent context resources from K2 commit 48b120b, including its four-argument prepare ABI and borrowed-device lifecycle. Preserve resource-contract admission before context mutation. Validate callable signatures and canonical storage before registration. Provide runner-backed CPU transport and trusted-view TMR consumption without enabling public launch or fabricating resource providers. Cover prepare rejection, consumption transactions and bounded templates. Fix shared buffer-pool lint without changing release-once semantics. Co-authored-by: YunjiQin <a1339924773@gmail.com>
Fix persistent argument ownership across partial preparation and failed release, including retryable kernel topology query cleanup. Copy context configuration at init and resolve topology before publishing resident arguments. Consume invocation arguments and callable tables explicitly in the existing executor and scheduler while preserving the program data sources and public layouts. Add bounded provider views, failure-reuse tests, and lifecycle coverage. Keep public kernel launch disabled pending resource providers and the execution binder. Targeted tests, sanitizers, program sim regression, and incremental lint pass within the documented validation scope.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks 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 |
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.
Motivation
K4 supplies an invocation snapshot, but the TMR executor also needs context-stable configuration and persistent AICore entry arguments. Passing each invocation by rewriting a shared Runtime would mix these lifetimes and undermine asynchronous graph execution.
This PR separates their sources while retaining the existing KernelArgs/Runtime layouts and reusing the executor, scheduler, and K2 resource owner:
The two lifetimes remain separate: invocation processing does not update the resident callable ID, function table, or entry-argument fields. Workers and teardown gates remain mutable execution state; this PR does not claim the entire Runtime is immutable.
Resident contract and execution adaptation
Context configuration and persistent arguments
src/common/platform/include/host/kernel_static_config.hintroducesKernelStaticConfig, a Host-owned copy of the context request and its generation. Packed input is read throughmemcpy; the caller's configuration pointer is not retained. Invalid thread counts are rejected. Kernel mode snapshots the existingSIMPLER_TMR_SERIAL_ORCH_SCHED_ENABLEsetting at init; program mode retains its existing configuration behavior.The first prepare resolves worker/thread counts and architecture-specific affinity before publishing persistent arguments. Later prepares reuse the fixed configuration and resident allocations. Kernel diagnostics requests remain explicitly unsupported, with resident DFX fields zeroed. The a2a3 FFTS fields and a5 SIMT anchor are preserved.
DeviceRunnerBase::prepare_kernel_callable()performs topology resolution and runtime-specific static configuration before calling the existingPersistentKernelArgs::prepare_once(). Configuration is fixed at the context level, not selected by the first or last registered callable. The prepare ABI does not accept replacement configuration, and invocation consumption does not update it.The resident TMR upload remains the
DeviceRuntimeLaunchDescprefix of Runtime, not its Host-only members. The physical layout is unchanged: callable ID, function table, orchestration arguments, and execution-memory placeholders retain their empty/sentinel values. Their presence in the layout does not make them kernel invocation inputs.Persistent ownership and failure recovery
src/common/platform/onboard/host/kernel_persistent_args.{h,cpp}retains the existingPersistentKernelArgsowner for the Runtime image, register-address table, and device KernelArgs. Its failure handling is tightened:The a5 kernel topology query also retains its temporary allocation and pending stream after submission/synchronization failure. Cleanup must establish completion before freeing that buffer. A failed free on the successful query path must not publish cached topology or report success. This handling does not reset a caller-owned device.
TMR-specific invocation views and consumers
src/common/tensormap_and_ringbuffer/kernel_execution_inputs.hdefines internal borrowed views, not another wire format or resource registry:KernelBindingViewKernelCallableViewExecutionInputsAdmission checks region bounds/alignment, size arithmetic, RuntimeContext placement, and function-table bounds before consuming the K4 packet. K4 validates the packet against the independent trusted identities. The provider must already supply a valid initialized image and keep all borrowed resources alive; matching generations alone do not establish lifetime safety.
device_binding_addrremains opaque.Each existing AicpuExecutor owns one
KernelInvocationStatewith its argument storage. The internal phases areadmit_kernel_execution,init_kernel_execution,run_kernel_execution,kernel_execution_status, andrelease_kernel_execution. An active invocation rejects replacement; this guard is not a concurrency lock.KernelInvocationState::admit()invokes K4'sconsume_tmr_invocation()once and materializes descriptors/scalars into executor-ownedEntryArgsStorage. It does not copy Tensor data, replace K4 decoding, allocate a per-invocation device buffer, or create a large temporary Runtime on the stack. Failed admission publishes no execution inputs.The implementations in both
src/{a2a3,a5}/runtime/tensormap_and_ringbuffer/aicpu/aicpu_executor.cppfeed these inputs into the existing execution body. HBG does not consume this TMR view or acquire a TMR payload requirement.Both architectures consume the explicit inputs throughout:
No new public C API, K9/K4 packet layout, device argument pool, or callable manager is introduced.
Lifecycle and program-path impact
Execution transaction: The caller owns the publication and completion protocol around the internal consumer phases:
Ownership: Config and orchestration borrow the same executor-owned argument storage through
orch_args_cached_. On normal completion, the final execution thread unbinds the current callable's orchestration Runtime. Release resets scheduler references and cached arguments before clearing storage. The provider keeps the resident Runtime, arenas, SM, function table, and callable images alive through their last device use; a generation match is not a retain operation.Serialization: Admission and release require one owner. The runtime DSO's executor is a shared static instance: protection must cover every user of that instance, including mixed program/kernel use, rather than only one context's lock. No additional mutex, registry, or implicit launch synchronization is introduced. A successful native enqueue is not evidence that storage can be reused.
Program compatibility: Program entry wrappers continue to obtain their inputs from the original Runtime. Program bind/staging and per-run resource preparation are not redirected through the kernel path. Shared executor/scheduler code is modified, so this is not a claim of zero program-source impact; valid program behavior is covered by regression testing. The a2a3 program cleanup retains its Runtime cache invalidation, while kernel cleanup does not assume that the next invocation will refresh the resident Runtime from Host memory.
Dependencies: K2 supplies the resident owner and context facilities; K4 supplies the packet and argument conversion. K3/K10 retain responsibility for execution-resource and callable publication/lifetime. K7/binder supplies platform entry setup, execution ordering, cancellation, completion, and reuse protection. This PR exposes callable consumption phases but does not register an incomplete public device entry.
simpler_kernel_mode_supported()remains0; public launch still rejects execution. Sim kernel init retains its validated unsupported endpoint, and HBG kernel init is not enabled by this TMR change.Tests
Results describe the source snapshot published as
b6435e48. Commit-subject relabeling preserved every source tree; it did not constitute a new test run. These results are not a completed GitHub CI run.Coverage added in this PR
tests/ut/cpp/common/test_kernel_persistent_args.cpp: Allocation/copy/partial-architecture failures, retained ownership after failed rollback, readiness revocation, retrying only remaining allocations, and abandonment. Shared owner tests cover TMR/HBG on both architectures.tests/ut/cpp/common/test_kernel_execution_inputs.cpp: Packed configuration ownership, replacement rejection, binding bounds/alignment, stale identity rejection, unchanged published inputs on failure, program storage borrowing, and empty-Tensor conversion compatibility.tests/ut/cpp/common/test_tmr_scheduler_execution_inputs.cpp: 6 cases per architecture, covering both handshake binding sites, A/B/A function selection, ordinary/early/gated payload publication, invalid mappings, and program wrappers.tests/ut/cpp/common/test_tmr_executor_execution_inputs.cpp: 4 cases per architecture, covering actual config/orchestration consumption, serial/nonserial A/B/A reuse, failed admission, early init rejection, and config failure followed by release and successful execution of another invocation.tests/ut/cpp/hardware/test_a5_kernel_topology_lifecycle.cpp: 9 cases using the real onboard Host implementation with injected runtime operations. They check allocation/copy/launch/sync/free failures, retained ownership, close retries, and the absence of device reset or ACL finalization.tests/ut/py/test_kernel_mode_c_api.py: Real sim entry checks for invalid and unsupported kernel configuration without enabling execution.Validation results
The executor tests compile the real executor, scheduler, runtime, and SO loader. They exercise K4 admission followed by actual config/orchestration calls, serial/nonserial A/B/A reuse, transport-buffer overwrite, and resident-field preservation. The orchestration fixture submits a dependency-only task; actual AICore execution is not claimed. Child function-table selection is covered separately through the real scheduler's ordinary/early/gated paths.
The supplementary tests cover early init configuration rejection and config expected-count mismatch. Both threads return, release runs, and a new invocation succeeds with its own arguments. They do not establish recovery from every partial handshake or asynchronous device cancellation.
Topology fault injection exercises the actual a5 onboard Host implementation without NPU execution. Isolated builds use explicit ccache and four-way parallelism; existing Torch dependencies are not installed or replaced.
Reproduction
After the repository's isolated build setup, the focused executor tests can be rebuilt and run with:
Use a separate build directory with
-fsanitize=address,undefined -fno-omit-frame-pointerfor the sanitizer run. The a5 topology target additionally requires the existing hardware-test CMake option, CANN development dependencies, and the built a5 onboard Host library; its injected Host test does not execute an NPU kernel.Remaining acceptance
These remain integration acceptance items. Host fixtures, owner counters, and program simulation do not substitute for them. The earlier K4 CPU transport probe is not counted as K5 executor or graph-execution evidence.
Commits
The K5 implementation is b6435e48 —
Add: isolate TMR resident and invocation state(K5): persistent ownership fixes, immutable context configuration, explicit invocation consumption in both executors/schedulers, and associated tests.Earlier commits are prerequisite code, with component labels added to their subjects. Their source trees are unchanged:
66156ed3dc1268cd9e5492b148e37b3bdb2553e72153406ab5529c893e822453c73001d6b0943525The dependency chain is K1 → {2a, K2} → K4 → this K5 PR. K2's implementation is already included in the K4 integration snapshot; this branch does not introduce a separate K2 merge commit. For a focused review, compare
c73001d6...b6435e48rather than attributing the entire branch-to-main diff to K5.中文总结
b0943525,包含 K1、2a 和 K2/K4 组合实现。当前 K5 提交为b6435e48;前序提交仅整理标题,代码树不变。等待前置合入后对齐复验,保持 Draft。