Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add named manual kernel registration API - #20658

Open
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration
Open

Add named manual kernel registration API#20658
goutamadwant wants to merge 1 commit into
pytorch:mainfrom
goutamadwant:11221-named-manual-kernel-registration

Conversation

@goutamadwant

@goutamadwantgoutamadwant commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 as register_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_REGISTRATION explicitly 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_build
  • python3 -m py_compile codegen/gen.py codegen/test/test_executorch_gen.py
  • Changed-file lintrunner checks
  • Configured and installed the ExecuTorch CMake targets locally
  • Configured and built the named_manual_registration_test target with TEST_NAMED_MANUAL_REGISTRATION=ON
  • Ran named_manual_registration_test, which registers two generated libraries and verifies both operators are present
  • git diff --check

cc @larryliu0820@JacobSzwejbka@lucylq

@pytorch-bot

pytorch-botBot commented Jul 1, 2026

Copy link
Copy Markdown

🔗 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.

⚠️ 17 Awaiting Approval

As of commit 1b978a7 with merge base ed65b12 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 1, 2026
@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: api"

@pytorch-botpytorch-botBot added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Jul 1, 2026
@nil-is-allnil-is-all added the module: runtime Issues related to the core runtime and code under runtime/ label Jul 7, 2026
@nil-is-all

Copy link
Copy Markdown
Contributor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Hi @goutamadwant, thanks for the PR! Could you address the errors in the lintrunner logs? Should be simple fixes

Sure will take a look @nil-is-all

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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

Copy link
Copy Markdown
Contributor

@claude review this PR

@claude

claudeBot commented Jul 9, 2026

Copy link
Copy Markdown

Claude finished @digantdesai's task in 4m 34s —— View job


PR Review: Named manual kernel registration API

I reviewed the diff against origin/main. Overall this is a clean, well-scoped, backward-compatible change with good Python-level test coverage. Details below.

What's good

  • Backward compatible.get_manual_registration_function_name returns register_all_kernels when no lib name is given, and the templates default to that via the DEFAULT_MANUAL_REGISTRATION_FUNCTION_NAME env value, so existing consumers are unaffected.
  • Validation is sensible. Rejecting --manual-registration-lib-name without --manual-registration, and enforcing a valid C++ identifier, both fail fast with clear messages surfaced through parser.error.
  • Good unit tests.TestManualRegistrationFunctionName and TestManualRegistrationTemplates cover the default, named, and both error paths, plus actual template rendering for .h and .cpp.
  • Buck + CMake plumbing is threaded through consistently, and the write_sharded(..., base_env=...) approach is the right way to inject a non-sharded value into the aggregate RegisterKernelsEverything.cpp.

Issues / suggestions

1. CMake couples the C++ function name to the CMake target name (codegen/gen.py validation + tools/cmake/Codegen.cmake:210-212)

list(APPEND _gen_command --manual-registration
--manual-registration-lib-name=${GEN_LIB_NAME})

GEN_LIB_NAME is a CMake target name, which is not guaranteed to be a valid C++ identifier (targets legitimately contain -, ., ::). If a caller enables MANUAL_REGISTRATION on such a target, codegen will parser.error("... must be a valid C++ identifier") and hard-fail the build. Worth either sanitizing the name before passing it (e.g. replace non-[A-Za-z0-9_] with _) or documenting the constraint next to the MANUAL_REGISTRATION option. Fix this →

2. The CMake MANUAL_REGISTRATION path has no in-tree caller and is therefore untested.
Grepping the repo, MANUAL_REGISTRATION is only referenced inside tools/cmake/Codegen.cmake itself — no CMakeLists.txt passes it to generate_bindings_for_kernels/gen_operators_lib. Combined with the author's note that CMake couldn't be run locally, the entire CMake branch (including the new RegisterKernelsEverything.cpp / RegisterKernels.h source list) is effectively unexercised. Two suggestions:

  • Confirm the sharded aggregate output filename really is RegisterKernelsEverything.cpp for the manual path (the Python test relies on this, so it should be correct — but the CMake add_custom_command OUTPUT must match exactly or the build will error on a missing output).
  • Consider wiring one existing lib (or a test lib) through the CMake path in a follow-up so CI covers it.

3. Very generic CLI aliases (codegen/gen.py:997-1004).
--lib-name / --lib_name are broad names for what is specifically the manual-registration lib name. Since codegen already has a LIB_NAME concept elsewhere, these short aliases could invite confusion. Consider dropping them and keeping just --manual-registration-lib-name (+ underscore variant).

4. Minor: redundant anchors in the regex.
re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") combined with .fullmatch() double-anchors; fullmatch already anchors both ends. Harmless, just noise.

Test verification

I attempted to run codegen.test.test_executorch_gen locally but the sandbox blocked the Python invocation, so I could not independently execute the suite. The tests read correctly and the template/base_env wiring is sound on inspection. The CI Lint/pull jobs (currently awaiting approval) should confirm.

Verdict

No 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.
· branch 11221-named-manual-kernel-registration

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

@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 -, ., or :: now produce a valid registration function name instead of causing codegen to fail.

I also updated the focused codegen tests and reran the changed-file lintrunner check locally. The new workflow runs are currently waiting for approval.

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CMake↔codegen filename parity. The MANUAL_REGISTRATION branch swaps the generated sources to RegisterKernelsEverything.cpp / RegisterKernels.h (vs RegisterCodegenUnboxedKernelsEverything.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.

  2. CMake passes the raw LIB_NAME as 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

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review @shoumikhin I removed the generic --lib-name and --lib_name aliases, keeping --manual-registration-lib-name and its underscore variant.

For your other two points:

  • The manual registration tests generate and read RegisterKernelsEverything.cpp and RegisterKernels.h, matching the filenames used by CMake.
  • CMake can continue passing the target name directly because codegen sanitizes it before embedding it in the C++ registration symbol. The sanitization is covered by the portable-ops.lib::debug test case.

I also reran the focused manual registration tests and the changed-file lintrunner checks. Let me know if any other suggestions. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from a holistic design and implementation review.

Comment threadcodegen/gen.py Outdated


def sanitize_manual_registration_lib_name(lib_name: str) -> str:
sanitized = MANUAL_REGISTRATION_LIB_NAME_SANITIZE_PATTERN.sub("_", lib_name).strip(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link
Copy Markdown
ContributorAuthor

@shoumikhin addressed your comments / suggestions. let me know if this looks good. thanks!

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).

  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@goutamadwant

Copy link
Copy Markdown
ContributorAuthor

Two things I'd like addressed on this.

  1. The help text and the bzl docstring describe behavior the code does not implement. Both the --manual-registration-lib-name help in codegen/gen.py and the executorch_generated_lib docstring in shim_et/xplat/executorch/codegen/codegen.bzl say "Characters that are not valid in a C++ identifier are converted to underscores." The code does the opposite: get_manual_registration_function_name raises ValueError on such a name, and generate_bindings_for_kernels in tools/cmake/Codegen.cmake hits FATAL_ERROR. So a name like my-ops.lib is a hard build failure, not a sanitized register_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).
  2. In tools/cmake/Codegen.cmake, gen_operators_lib recovers the manual-registration flag by reading a GLOBAL property keyed on SHA256(LIB_NAME) that generate_bindings_for_kernels set earlier. This is an implicit contract between two separate function calls with an ordering hazard: if gen_operators_lib runs before generate_bindings_for_kernels for the same LIB_NAME, the property is empty, it silently selects RegisterCodegenUnboxedKernelsEverything.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 a MANUAL_REGISTRATION option to gen_operators_lib, symmetric with generate_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, since LIB_NAME is already validated as a C++ identifier and is a legal property suffix, so the hash only hides the key from cmake --trace.

@shoumikhin Addressed both these points in the latest push. below are the highlights.

  • Updated the CLI help and Starlark documentation to describe the strict C++ identifier requirement.
  • Removed the global property and passed MANUAL_REGISTRATION explicitly to gen_operators_lib.
  • Updated the CMake caller to pass the option to both codegen functions.

test pass locally. Let me know if there are any other suggestions/comments.

Signed-off-by: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
@goutamadwant
goutamadwantforce-pushed the 11221-named-manual-kernel-registration branch from 643919e to 1b978a7CompareAugust 15, 2026 06:56

@shoumikhinshoumikhin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. A consumer that links the library still cannot include the generated header, because gen_operators_lib never sets an include directory on the target. The new example works around this with its own target_include_directories.
  2. Two of these libraries cannot both be installed, because the generated header goes into PUBLIC_HEADER and CMake flattens those to basenames at install time.
  3. docs/source/using-executorch-faqs.md:77 still tells users there must be only one generated operator library per target, which this PR's own example contradicts. Nothing in docs/ changes here.
  4. 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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment threadcodegen/gen.py
manual_registration: bool,
manual_registration_lib_name: str | None,
) -> str:
if not manual_registration_lib_name:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: runtimeIssues related to the core runtime and code under runtime/release notes: apiChanges to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual kernel registration to include library names in API

4 participants

@goutamadwant@nil-is-all@digantdesai@shoumikhin