Add: kernel-mode entry on Worker(level=2) and a pre-init capability probe - #2248
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced 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 |
…robe PyPTO drives simpler through simpler.worker.Worker, but the kernel-mode entries existed only on ChipWorker. A level-2 Worker now runs in one of two execution modes fixed at construction, so a framework process that already owns a device and a stream can bind, prepare, launch and close a kernel context through the public Worker. Worker(level=2, execution_mode="kernel", device_id=..., platform=..., runtime=...) accepts init(config=CallConfig(...)), which routes to ChipWorker.kernel_init on the device the calling thread already has current. Nothing is prewarmed, no registration is replayed and no SDMA workspace is provisioned. config is keyword-only and required in kernel mode; prewarm_config is refused there, and config= is refused in program mode. Kernel mode has no L3+ form, because an L3 chip lives in a forked child that cannot see the caller's current device or stream, and it is refused at construction for any other level. kernel_prepare_callable(chip_callable) returns the id the runtime minted. It is unrelated to register(): there is no digest dedup, no handle and no pre-init recording, so the same callable prepared twice takes two ids. The Worker keeps every prepared image referenced until a native teardown succeeds, because the device holds addresses into it. kernel_launch(callable_id, args, *, caller_stream) enqueues one invocation on the caller's stream and returns. It takes no operation lease, creates no RunHandle and waits for nothing. args is ChipStorageTaskArgs; the stream is keyword-only and nonzero; an id this Worker did not mint, or a non-integer id, is refused before the native call. kernel_mode_supported answers before init. The new static ChipWorker::probe_kernel_mode_supported loads the host runtime, asks simpler_kernel_mode_supported on a fresh device context that no init touches, and destroys that context before the library handle is released; the C ABI requires that entry to answer from the runtime build alone, so the probe takes no device. The Worker caches the answer, answers False for a level other than 2, and answers True for a READY kernel-mode Worker without probing. A missing runtime build raises instead of reading as unsupported. The two modes exclude each other. A kernel-mode Worker refuses register, unregister, submit, run, malloc, free, copy_to, copy_from, create_buffer, make_tensor_arg, release_buffer and device_memory_info; the native memory query already refuses a kernel context, and the rest would reach program state a kernel context does not have. committed_device_memory stays available. A program-mode Worker refuses the kernel entries. A lock linearizes prepare and launch against the native finalize. A launch that overlaps a prepare, another launch or close() fails immediately, since the caller serializes them. close() publishes CLOSED before it takes the lock, so a launch either observes CLOSED or finishes before finalize; if a prepare or launch still holds the lock after the rollback grace period, close() raises and can be called again. On this base ChipWorker.finalize returns without raising when the device teardown fails and leaves the native worker initialized, so kernel close() checks that state and raises, which keeps the CleanupJournal entry, the ChipWorker, its images and the GC pin for a retry. The process that bound the context is recorded, and a forked child that inherited the Worker cannot prepare, launch or tear it down. ~ChipWorker finalizes on whichever thread destroys it, without the quiescence kernel teardown requires. A kernel-mode Worker garbage-collected without close(), or still alive at interpreter exit, therefore emits a ResourceWarning and deliberately pins its ChipWorker for the rest of the process instead of letting that destructor run. The finalizer receives only the pinned object, never the Worker. The Worker and init/close docstrings, docs/user/reference/python-api.md, docs/chip-level-arch.md and the kernel-mode integration test page describe the mode, its preconditions and its refusals. The ChipWorker.kernel_prepare_callable docstring and the residency guide state that registration synchronizes the context's own stream, so a registration failure raises from prepare. test_worker_kernel_mode.py covers the Worker against a fake ChipWorker: argument and mode validation, routing, the probe, id minting, launch forwarding and refusals, the gate against prepare, launch and close, retryable teardown, failed-init rollback, the GC pin and the process fence, plus the real a2a3sim build reporting no kernel mode. test_chip_worker.py drives the probe against the generated fake runtime. test_worker_kernel_mode_hw.py runs on a2a3 without torch: the probe before init, eager launches with exact numerics on the caller's stream, repeated prepares, Worker- and runtime-side refusals followed by a valid launch, a second Worker refused on the same device, and host_build_graph refused, each case checking that the caller's stream and device tear down cleanly afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebasing onto the current integration head brings in hw-native-sys#2247, which changed the contract this branch's close() compensated for. ChipWorker.finalize now raises ChipWorkerError when a kernel context's device teardown fails, and clears initialized before it does, so the Worker-side check for a still-initialized native worker after a returning finalize() can never fire: raising is already what keeps the CleanupJournal entry, the ChipWorker, its prepared images and the GC pin for a later close(). The fake ChipWorker in test_worker_kernel_mode.py modelled the old behaviour — a failed finalize returned and left the native worker initialized — so leaving it would have kept asserting a contract the real one no longer has. It now clears initialized and raises, and test_failed_teardown_keeps_the_context_for_retry expects ChipWorkerError with the status the entry reports. Three of the four rebase conflicts resolve by keeping both sides: probe_kernel_mode_supported joins hw-native-sys#2247's finalize() doc in chip_worker.h, TestChipWorkerKernelProbe joins TestChipWorkerKernelEntryLayer, and the fake runtime keeps hw-native-sys#2247's live-handle tracking alongside this branch's created-context counter. The kernel_prepare_callable docstring takes this branch's account of the registration synchronize plus hw-native-sys#2247's sentence on how long the image stays referenced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
05daa33 to
ef66b5e
Compare
|
Pushed a rebase onto Rebase: three conflicts, all resolved by keeping both sidesThey all come from #2247 landing after this branch's base.
Follow-up commit: one compensation #2247 made unreachable
self._chip_worker.finalize()
# ChipWorker.finalize returns without raising when the native device teardown
# fails, and the native worker then stays initialized. ...
if kernel and impl is not None and bool(getattr(impl, "initialized", False)):
raise RuntimeError("kernel context teardown failed; ...")After #2247 that comment is false and the branch is dead. Removed it. The fake Also now stale, left for youThree of the four entries under "Known issues found on the way" are fixed on this head by #2247:
The fourth — Verified on the rebased branch
Hardware ran through One note on the review thread that led here: I said earlier the hardware cases did not cover close invalidating the minted ids. That was wrong — I had read the summary rather than the code. #2249 is the capture scene-test fix and is disjoint from this: it changes no Worker or ChipWorker code, and this changes no scene test, so they can land in either order. |
209b3b8
into
hw-native-sys:feat/kernel-mode-integration-test
Targets
feat/kernel-mode-integration-test.What this is
PyPTO drives simpler through
simpler.worker.Worker, but the kernel-mode entries on this branch exist only onChipWorker. YunjiQin raised this on #2185 (the PyPTO integration target isWorker). This PR adds a kernel mode toWorker(level=2), so a framework process that already owns a device and a stream can use kernel mode through the publicWorker.API
Worker(level=2, execution_mode="kernel", ...)"program"is the default. A level other than 2 raisesValueError, and so doesenable_sdma.init(*, config)ChipWorker.kernel_initon the device the calling thread already has current.configis required and keyword-only.prewarm_configis refused. Nothing is prewarmed or replayed.kernel_mode_supportedFalsefor a level other than 2. A missing runtime build raises.kernel_prepare_callable(chip_callable) -> intregister()is not involved. Images stay referenced until a native teardown succeeds.kernel_launch(callable_id, args, *, caller_stream) -> NoneRunHandleand no wait.argsisChipStorageTaskArgs. An id this Worker did not mint, a non-integer id, or a zero stream is refused before the native call.close()close()retries.Mode exclusion.
register/unregister/submit/run,malloc/free/copy_to/copy_from,create_buffer/make_tensor_arg/release_bufferanddevice_memory_info. The native memory query already refuses a kernel context.committed_device_memorystays available.Decisions taken for this PR
Worker layer only, on this branch's
ChipWorker. The Add: kernel-mode entry layer — give the four C entries callers (⑩a) #2185-headChipWorkerfixes (typed errors, teardown owed after a failed init) are not ported here. Where this base'sChipWorkerfalls short, the Worker compensates.ChipWorker.finalizeswallows a device-teardown failure, so kernelclose()checksinitializedafterwards and raises to keep the journal entry. The Worker also keeps its own reference to every prepared image.kernel_launchtakesChipStorageTaskArgs. The borrowed-tensor argument type is a follow-up; its public name is still undecided.A real pre-init probe. This is the only C++ change. The new static
ChipWorker::probe_kernel_mode_supported(host_lib_path, sim_context_path):simpler_kernel_mode_supportedon a fresh device context that no init touches;The C ABI already requires that entry to answer from the runtime build alone, so the probe takes no device. It is bound on
_ChipWorkerand wrapped asChipWorker.probe_kernel_mode_supported(bins).An unclosed kernel Worker leaks rather than finalizes.
~ChipWorkerfinalizes on whatever thread destroys it, without the quiescence kernel teardown requires.ResourceWarningand deliberately pins itsChipWorkerfor the rest of the process. The finalizer never references the Worker.Concurrency, teardown, fork
close()fails fast. The caller is expected to serialize these calls.close()publishes CLOSED before it takes the lock, so a launch either sees CLOSED or finishes before finalize.close()raisesTimeoutErrorand can be called again.kernel_initrolls back to FAILED through the existing path. Nothing is pinned.Validation
CI does not run for PRs into this branch (
ci.ymltriggers on PRs tomain), so everything below was run by hand on a2a3 silicon. Hardware runs were wrapped intask-submit.test_worker_kernel_mode.py+test_chip_worker.py+test_startup_readiness.pyimport torchset this box always hits (no torch installed); the new file adds none.test_kernel_mode_entrytest_worker_kernel_mode_hw.pycases (H0 probe before init, H1 eager numerics, H2 prepare twice, H3 refusals then a valid launch, H4 second Worker on the same device, H5 HBG refused)Baseline on the untouched branch (
4a5f28c9f), same box:import torch(this box has no torch).Out of scope
ChipWorkerfixes on this branch: typed error codes,finalize_deviceafter a failedkernel_init, and teardown-failure propagation.Known issues found on the way (not fixed here)
ChipWorker::kernel_initdestroys the context withoutfinalize_deviceafter a nonzero init rc.ChipWorker::finalizeswallows a device-teardown failure, in program mode too. The PythonChipWorker.finalizewrapper then clears its registries.~ChipWorkerfinalizes on the destroying thread.chip_worker.hdoc forkernel_prepare_callablestill describes acaller_streamparameter.🤖 Generated with Claude Code