Skip to content

[CoreAI] Run enumerated-shape exports by dispatching on input shape - #24525

Merged
metascroy merged 3 commits into
coreai-expect-frequent-reshapesfrom
coreai-enumerated-shapes
Nov 4, 2026
Merged

metascroy merged 3 commits into
coreai-expect-frequent-reshapesfrom
coreai-enumerated-shapes

Conversation

@metascroy

@metascroy metascroy commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Makes Core AI exports that use input_enumerations load and run, including with multi-function export.

Stacked on #24522.

Problem

Every enumerated-shape PTE failed at load_method:

Core AI execution error: Model has no function named 'main'   (0x23)

AIProgram.set_static_shape_config erases the delegate's dynamic main and replaces it with one static clone per shape (main_s31_4, main_s31_16, ...). The manifest still named main, and the runtime binds exactly that name. On the real SDK, a specialized model exposes only the clones, each with a static input descriptor.

Export

An enumerated delegate's manifest lists its per-shape functions instead of one function:

"enumerated_functions": [
  {"function": "main_s31_4",  "input_shapes": [[4, 8]]},
  {"function": "main_s31_16", "input_shapes": [[16, 8]]},
  {"function": "main_s31_32", "input_shapes": [[32, 8]]}
]

input_shapes gives every input's shape in input_names order, the same order ExecuTorch passes arguments in. Delegates without enumerations keep function: "main".

If an enumerated value makes a derived boundary dim zero or negative (s0 - 1 at s0=1), export fails with an error naming the input, the dim and the value.

Runtime

  • Manifest parsing: a manifest must have exactly one of function and enumerated_functions. Enumerated entries must have distinct names, one shape per input, positive integer dims, and no two entries with the same shapes. Enumerated delegates carry an explicit enumerated flag.
  • Init: binds one session per listed function. Any bind failure fails init.
  • Execute: validates the arguments once, then picks the function whose shapes equal the inputs. This happens inside the per-model execution lock. Inputs that match none fail with InvalidArgument before anything runs, and the log names the shapes received.
  • Sharing: all functions come from one model, so they share weights, one specialization and one cache entry.
  • Cost: the number of functions is the product of the enumerated value counts. On an M4 Max, a warm load took 7 ms with 1 function, 41 ms with 16 and 248 ms with 64 (documented in the README).

Multi-function export

CoreAIPartitioner(multifunction=True) accepts input_enumerations. Give each method its own partitioner to enumerate only some methods, or different values per method:

partitioner={
    "decode":  [CoreAIPartitioner(multifunction=True)],
    "prefill": [CoreAIPartitioner(multifunction=True, input_enumerations=[{0: [64, 128]}])],
}
  • Naming: each enumerated partition expands into its own functions in the one shared model, prefill_0_<key>, plus prefill_1_<key> past a graph break. Unenumerated partitions stay single functions (decode_0).
  • Binding order: every partition is bound before any is specialized, because specializing a partition erases its function.
  • Compile specs: input_enumerations may differ between methods. Every other compile spec (AOT config, deployment floor) must match.
  • Errors: export fails with a clear error when some methods omit multifunction=True, naming those methods, and when two delegates would generate the same function name.
  • Runtime sharing: works with coreai_share_multifunction_model; every delegate and per-shape function shares one loaded model.

Shared-load robustness

This PR also tightens #24476's per-key shared loading:

  • A scope guard clears a load's in-flight key, publishes the model and wakes waiters on every exit, so a key can't stay marked as loading.
  • WaiterAcquiresAfterConcurrentSharedLoadFails confirms the second load is actually waiting before the first one fails. It uses a test-only BeforeSharedLoadWait fault point.

Test plan

Python. All 171 CoreAI tests pass.

  • test_shape_config.py: test_manifest_lists_one_function_per_shape, test_unenumerated_lowering_keeps_main, test_derived_dim_that_reaches_zero_is_rejected. The real-runtime test test_runs_each_enumerated_shape_and_rejects_others runs each shape and checks rejection.
  • test_multifunction.py:
    • test_enumerated_method_lists_per_shape_functions, test_graph_break_enumerates_every_partition.
    • test_other_compile_specs_must_still_match, test_mixed_partitioners_name_the_missing_flag, test_duplicate_function_names_are_rejected.
    • The real-runtime test test_enumerated_shapes_dispatch_by_input_shape covers multi-method with a graph break.

Native. All 6 CTest targets pass.

  • ParsesEnumeratedFunctions, RejectsInvalidEnumeratedFunctions (16 malformed cases, plus both or neither field present).
  • EnumeratedFunctionsDispatchByInputShape: checks which function ran, the resized output, and that an unmatched shape runs nothing.
  • EnumeratedFunctionBindFailureFailsInit.
  • The sharing tests, including WaiterAcquiresAfterConcurrentSharedLoadFails, passed in 5 of 5 runs.

Real SDK, M4 Max:

  • Single method: rows 4/16/32 match eager (max error ≤1.8e-7) on cold and warm loads. Rows 8 is rejected with InvalidArgument.
  • Multi-method, C++ runner: a static forward plus an enumerated prefill, with a graph break. That's 4 delegates and 8 functions in one model. With sharing off and on, cold and warm, 100 of 100 calls pass in each configuration (worst max error 1.4e-6), and an unmatched shape is rejected with 0x12.
  • AOT (aimodelc, h16c): the enumerated functions compile and run (max error ≤1.8e-7).

Compatibility

PTEs exported with input_enumerations before this change never loaded, so nothing that worked before breaks. Every other export keeps the function manifest field.

@metascroy
metascroy requested a review from shoumikhin as a code owner November 3, 2026 20:04
@pytorch-bot

pytorch-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24525

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 1 Unrelated Failure

As of commit 6eab989 with merge base ee4b85b (image):

NEW FAILURES - The following jobs have failed:

FLAKY - The following job failed but was likely due to flakiness present on trunk:

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

@meta-cla meta-cla Bot 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 Nov 3, 2026
@metascroy
metascroy force-pushed the coreai-expect-frequent-reshapes branch from b2fb6ae to 6e9fd39 Compare November 3, 2026 21:15
@metascroy
metascroy force-pushed the coreai-enumerated-shapes branch from 5cb7282 to fde433f Compare November 3, 2026 21:15
@metascroy
metascroy force-pushed the coreai-expect-frequent-reshapes branch from 6e9fd39 to 4827a47 Compare November 3, 2026 23:12
@metascroy
metascroy force-pushed the coreai-enumerated-shapes branch from 40a87bb to d60984d Compare November 3, 2026 23:12
Enumerated-shape exports could not load: set_static_shape_config erases the
delegate's dynamic function and replaces it with one static function per
shape, but the manifest still named the erased function, so every load failed
with "Model has no function named 'main'".

Export now lists the per-shape functions under enumerated_functions, each with
its input shapes in input_names order, instead of a single function. The
runtime binds every listed function at init and, per call, runs the one whose
shapes equal the inputs; inputs matching none fail with InvalidArgument before
anything runs. All functions come from one model, so they share weights, one
specialization and one cache entry.

Enumerations also work with multifunction export now. Each method's partitioner
may enumerate its own values (or none), and each enumerated partition expands
into <method>_<index>_<key> functions in the shared model. Every other compile
spec must still match across methods.
- Mixing multifunction and plain CoreAIPartitioners across methods now names
  the methods missing multifunction=True, instead of reporting that compile
  specs differ.
- Multi-function export rejects two delegates generating the same function
  name, which a method named like another method's <index>_<key> could cause.
- A derived boundary dim that an enumerated value makes non-positive (s0 - 1
  at s0=1) is rejected naming the input, dim and value, instead of failing in
  set_static_shape_config without them.
- README notes that enumerated function counts multiply across dims, with
  measured load times.
- Manifests mark enumerated delegates with an explicit flag that the handle
  carries, instead of select_session inferring it from empty input shapes.
- execute_session validates arguments once, then asks the delegate to pick a
  session for them, so select_session no longer repeats the tensor checks.
- A scope guard clears a shared load's in-flight key and wakes its waiters on
  every exit, so a key cannot stay marked as loading.
- WaiterAcquiresAfterConcurrentSharedLoadFails observes the waiter reaching
  the wait (new BeforeSharedLoadWait fault point) before failing the first
  load, instead of relying on a sleep.
- Partitioner comment notes that to_backend() would also split multifunction
  exports into separate models.
- README drops arrows from prose.
@metascroy
metascroy merged commit eb740de into coreai-expect-frequent-reshapes Nov 4, 2026
@metascroy
metascroy deleted the coreai-enumerated-shapes branch November 4, 2026 19:34

This branch was previously deployed

1 inactive deployment
cadence — 6eab9896 Deployed Nov 4, 2026 by metascroy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants