Uh oh!
There was an error while loading. Please reload this page.
Add named manual kernel registration API - #20658
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20658
Note: Links to docs will display an error until the docs builds have been completed.
|
goutamadwant
commented
Jul 1, 2026
@pytorchbot label "release notes: api" |
nil-is-all
commented
Jul 7, 2026
Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes |
goutamadwant
commented
Jul 7, 2026
Sure will take a look @nil-is-all |
goutamadwant
commented
Jul 8, 2026
@nil-is-all Addressed the lintrunner formatting issues and pushed the update. I reran the changed-file lintrunner check and the focused codegen unit tests locally. Let me know if there are any suggestions. thanks! |
digantdesai
commented
Jul 9, 2026
@claude review this PR |
Claude finished @digantdesai's task in 4m 34s —— View job PR Review: Named manual kernel registration APII reviewed the diff against What's good
Issues / suggestions1. CMake couples the C++ function name to the CMake target name ( list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})
2. The CMake
3. Very generic CLI aliases ( 4. Minor: redundant anchors in the regex. Test verificationI attempted to run VerdictNo blocking correctness bugs in the Python codegen. The main thing I'd want addressed before merge is #1 (CMake target-name → C++ identifier coupling), since it can turn into a confusing build failure. #2 is a coverage gap worth a follow-up. The rest are minor/nits. |
goutamadwant
commented
Jul 10, 2026
@claude review this again Addressed the CMake target-name concern by sanitizing the manual registration library name before it is embedded in the generated C++ symbol. For example, target/library names with characters like I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval. |
shoumikhin
left a comment
There was a problem hiding this comment.
Thanks for picking up #11221 — opt-in flag with the default preserved is the right shape, and doing Buck + CMake together is appreciated. Approving. A couple of non-blocking things worth a look:
CMake↔codegen filename parity. The
MANUAL_REGISTRATIONbranch swaps the generated sources toRegisterKernelsEverything.cpp/RegisterKernels.h(vsRegisterCodegenUnboxedKernelsEverything.cpp). Worth confirming codegen actually emits those names in that mode and the Buck path stays consistent — a mismatch here would be a silent build break.CMake passes the raw
LIB_NAMEas the identifier (--manual-registration-lib-name=${GEN_LIB_NAME}). The validator requires^[A-Za-z_][A-Za-z0-9_]*$, so a target whose name has a-or.would hard-error codegen. Either sanitize-/.→_or document that manual-registration targets need identifier-safe names.
Nit: the flag has four spellings (--manual-registration-lib-name / --manual_registration_lib_name / --lib-name / --lib_name) — one canonical plus the underscore alias is plenty.
Nice validation + codegen tests otherwise; this is the right fix for the multi-lib registration collision.
goutamadwant
commented
Jul 16, 2026
Thanks for the review @shoumikhin I removed the generic For your other two points:
I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks! |
shoumikhin
left a comment
There was a problem hiding this comment.
Inline findings from a holistic design and implementation review.
| def sanitize_manual_registration_lib_name(lib_name: str) -> str: | ||
| sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip( |
There was a problem hiding this comment.
This sanitization is lossy, so distinct libraries can still generate the same global symbol. For example, foo-bar, foo.bar, and foo_bar all become register_foo_bar_kernels(), causing duplicate definitions when those libraries are linked together. Could we either reject non-identifier names or use a collision-resistant encoding/hash suffix? Avoiding registration-symbol collisions is the primary purpose of this API.
There was a problem hiding this comment.
@shoumikhin I removed the lossy sanitization. --manual-registration-lib-name now requires a valid C++ identifier, so names such as foo-bar and foo.bar fail instead of collapsing to the same generated symbol. Valid identifier names also remain unchanged and I added tests for invalid punctuation and leading digits.
| set(_srcs_list ${_out_dir}/RegisterCodegenUnboxedKernelsEverything.cpp | ||
| ${_out_dir}/Functions.h ${_out_dir}/NativeFunctions.h | ||
| ) | ||
| if(GEN_MANUAL_REGISTRATION) |
There was a problem hiding this comment.
MANUAL_REGISTRATION must now be repeated independently in both generate_bindings_for_kernels() and gen_operators_lib(). If a caller enables it on only one call, codegen and target_sources() expect different filenames (RegisterKernelsEverything.cpp versus RegisterCodegenUnboxedKernelsEverything.cpp), resulting in a missing-source build failure. Could we derive/store this mode per LIB_NAME, combine the configuration, or at least fail at configure time when the two calls disagree?
There was a problem hiding this comment.
Updated this so generate_bindings_for_kernels() records the manual-registration mode for each LIB_NAME, and gen_operators_lib() derives the mode from that configuration. Callers should no longer repeat MANUAL_REGISTRATION in both calls.
| genrule_cmd = genrule_cmd + [ | ||
| "--manual_registration", | ||
| ] | ||
| if manual_registration_lib_name: |
There was a problem hiding this comment.
If manual_registration_lib_name is supplied without manual_registration = True, this macro silently ignores the name. Direct codegen rejects the same combination with --manual-registration-lib-name requires --manual-registration. Could we add a Starlark fail() here so the public Buck API has the same validation contract?
There was a problem hiding this comment.
Added a Starlark fail() when manual_registration_lib_name is supplied without manual_registration = True, matching the validation contract of the Python codegen entry point.
| if(GEN_ADD_EXCEPTION_BOUNDARY) | ||
| set(_gen_command "${_gen_command}" --add-exception-boundary) | ||
| endif() | ||
| if(GEN_MANUAL_REGISTRATION) |
There was a problem hiding this comment.
Could we add an end-to-end CMake test for this branch? No in-tree CMake caller currently enables MANUAL_REGISTRATION, and the added Python tests only verify template rendering. A useful regression test would build two manually registered libraries, include both generated headers, call both named functions, and verify both kernel sets register. That would also catch mismatches between custom-command outputs and target_sources().
There was a problem hiding this comment.
@shoumikhin added an end-to-end CMake regression to the existing portable custom-ops workflow. It builds two independently named manual-registration libraries, includes both generated headers, calls both generated registration functions, and verifies that both operators are present in the runtime registry.
Let me know!
goutamadwant
commented
Jul 21, 2026
@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks! |
shoumikhin
left a comment
There was a problem hiding this comment.
Two things I'd like addressed on this.
The help text and the bzl docstring describe behavior the code does not implement. Both the
--manual-registration-lib-namehelp incodegen/gen.pyand theexecutorch_generated_libdocstring inshim_et/xplat/executorch/codegen/codegen.bzlsay "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite:get_manual_registration_function_nameraisesValueErroron such a name, andgenerate_bindings_for_kernelsintools/cmake/Codegen.cmakehitsFATAL_ERROR. So a name likemy-ops.libis a hard build failure, not a sanitizedregister_my_ops_lib_kernels. Please update both strings to state that the lib name must already be a valid C++ identifier (I'd keep the strict validation and just correct the docs rather than add sanitization).In
tools/cmake/Codegen.cmake,gen_operators_librecovers the manual-registration flag by reading a GLOBAL property keyed onSHA256(LIB_NAME)thatgenerate_bindings_for_kernelsset earlier. This is an implicit contract between two separate function calls with an ordering hazard: ifgen_operators_libruns beforegenerate_bindings_for_kernelsfor the sameLIB_NAME, the property is empty, it silently selectsRegisterCodegenUnboxedKernelsEverything.cpp(which is never generated in manual mode), and the build fails at compile time with no indication of the cause. Please pass this explicitly instead: add aMANUAL_REGISTRATIONoption togen_operators_lib, symmetric withgenerate_bindings_for_kernels, and set it from the caller. That removes the hidden global state and the ordering trap. If you prefer to keep the property channel, at least drop the SHA256 hashing, sinceLIB_NAMEis already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key fromcmake --trace.
goutamadwant
commented
Jul 24, 2026
@shoumikhin Addressed both these points in the latest push. below are the highlights.
test pass locally. Let me know if there are any other suggestions/comments. |
Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
643919e to
1b978a7Compare
shoumikhin
left a comment
There was a problem hiding this comment.
The naming mechanism itself looks right, and the two things I asked for last time are done. My remaining concern is that this renames the symbol but does not quite finish making a second kernel library usable. Four things:
- A consumer that links the library still cannot include the generated header, because
gen_operators_libnever sets an include directory on the target. The new example works around this with its owntarget_include_directories. - Two of these libraries cannot both be installed, because the generated header goes into
PUBLIC_HEADERand CMake flattens those to basenames at install time. docs/source/using-executorch-faqs.md:77still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing indocs/changes here.- Two named libraries still abort at runtime if their operator sets overlap. That is a real constraint on the feature and it is neither documented nor tested.
Also, no build or test workflow has run on this head yet, they are all waiting for approval, so the new end to end test has not executed anywhere.
| @@ -360,9 +383,16 @@ function(gen_operators_lib) | |||
| add_library(${GEN_LIB_NAME}) | |||
There was a problem hiding this comment.
gen_operators_lib never calls target_include_directories on ${GEN_LIB_NAME}, so a consumer that links this library cannot include the generated RegisterKernels.h that the PR now publishes. That is why the new example has to add ${CMAKE_CURRENT_BINARY_DIR} to its own include path by hand. The prim ops helper in this same file does it the other way at line 208, and one target_include_directories(${GEN_LIB_NAME} INTERFACE $<BUILD_INTERFACE:${_out_dir}>) here would let consumers just link the target.
| set(_srcs_list ${_out_dir}/RegisterCodegenUnboxedKernelsEverything.cpp | ||
| ${_out_dir}/Functions.h ${_out_dir}/NativeFunctions.h | ||
| ) | ||
| if(GEN_MANUAL_REGISTRATION) |
There was a problem hiding this comment.
This is the consistency check I asked about earlier and I do not think it landed. If a caller passes MANUAL_REGISTRATION to only one of the two functions, the failure is Cannot find source file: .../RegisterKernelsEverything.cpp followed by No SOURCES given to target, and neither message mentions the option, so the user has no way to connect the error to the mistake. A get_source_file_property(<var> ${_out_dir}/<expected>.cpp GENERATED) check here with a FATAL_ERROR that names MANUAL_REGISTRATION would make it obvious.
| executorch_target_link_options_shared_lib(${GEN_LIB_NAME}) | ||
| set(_generated_headers ${_out_dir}/Functions.h ${_out_dir}/NativeFunctions.h) | ||
| if(GEN_MANUAL_REGISTRATION) | ||
| list(APPEND _generated_headers ${_out_dir}/RegisterKernels.h) |
There was a problem hiding this comment.
Adding RegisterKernels.h to PUBLIC_HEADER breaks the two library case at install time. CMake flattens PUBLIC_HEADER entries to basenames, and the in-tree pattern for installing one of these targets sends them all to one flat directory (see kernels/portable/CMakeLists.txt:101), so two manual registration libraries write the same filename and one is silently dropped. I reproduced it with two targets and one destination: the install log prints "Installing" then "Up-to-date" for the same path and only the first library's declaration survives. Installing under a per library subdirectory such as <includedir>/executorch/<lib_name>/ would fix it.
| message(STATUS " MANUAL_REGISTRATION: ${GEN_MANUAL_REGISTRATION}") | ||
| message(STATUS " DTYPE_SELECTIVE_BUILD: ${GEN_DTYPE_SELECTIVE_BUILD}") | ||
| if(GEN_MANUAL_REGISTRATION AND NOT GEN_LIB_NAME MATCHES |
There was a problem hiding this comment.
GEN_LIB_NAME is unquoted here, so when LIB_NAME is not passed at all CMake compares the literal string GEN_LIB_NAME against the regex, which matches, and the check silently passes. Verified with cmake -P on 3.31.8: unset is accepted, empty string is correctly rejected. Quoting it as NOT "${GEN_LIB_NAME}" MATCHES ... closes it.
| visibility = [], | ||
| aten_mode = False, | ||
| manual_registration = False, | ||
| manual_registration_lib_name = None, |
There was a problem hiding this comment.
This was inserted between manual_registration and use_default_aten_ops_lib, which shifts the thirteen parameters after it. Every in-tree caller uses keyword arguments so nothing here breaks, but the macro is public and does not require keyword-only calls, so an out-of-tree positional caller would silently bind use_default_aten_ops_lib to the new name. Appending it at the end of the signature avoids that at no cost.
| manual_registration: bool, | ||
| manual_registration_lib_name: str | None, | ||
| ) -> str: | ||
| if not manual_registration_lib_name: |
There was a problem hiding this comment.
Two inputs get through and regenerate the exact symbol this option exists to avoid. A name of all returns register_all_kernels, identical to the default, so an unnamed library and one named all collide at link time. An empty string short circuits before both the identifier check and the "requires manual registration" check, so an unset build variable expands to nothing and quietly falls back to the default. Testing if manual_registration_lib_name is None for omission and rejecting all covers both.
Summary
Adds an opt-in library-name parameter for manual kernel registration codegen so generated
RegisterKernels.{h,cpp}can expose a library-specific registration API such asregister_portable_ops_lib_kernels().The default manual registration API remains
register_all_kernels()when no library name is provided. Named registration requires a valid C++ identifier, preventing distinct library names from collapsing to the same generated symbol.CMake exposes
MANUAL_REGISTRATIONexplicitly on both code generation and operator-library creation, avoiding hidden cross-call state while keeping generated filenames and target sources aligned. The Buck macro supports the same optional name and rejects a name supplied without manual registration.Fixes#11221.
Test plan
PYTHONPATH=.. python3 -m unittest codegen.test.test_executorch_gen codegen.test.test_executorch_signatures codegen.test.test_executorch_types codegen.test.test_executorch_unboxing codegen.test.test_selective_buildpython3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.pylintrunnerchecksnamed_manual_registration_testtarget withTEST_NAMED_MANUAL_REGISTRATION=ONnamed_manual_registration_test, which registers two generated libraries and verifies both operators are presentgit diff --checkcc @larryliu0820@JacobSzwejbka@lucylq