Skip to content

compile_all, a library catalogue, and argument signatures - #313

Merged
sshin23 merged 9 commits into
mainfrom
anvil/compile-all-main
Aug 14, 2026
Merged

compile_all, a library catalogue, and argument signatures#313
sshin23 merged 9 commits into
mainfrom
anvil/compile-all-main

Conversation

@sshin23

@sshin23 sshin23 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Supersedes #312, whose content never reached main: it was stacked on anvil/rename-compiler and merged into that branch after #311 had already carried the branch into main — so the merge landed on a closed base. (Its third commit had additionally been pushed after GitHub took the merge snapshot.) This is the same three commits, cherry-picked onto current main and re-gated there.

compile_all — ExaModelsCompiler declares the verb and its keyword contract; a package that ships models implements compile_all(::Val{TheirModule}; …) in an extension, so providing models never costs a dependency on a compiler toolchain. Callers name the package — compile_all(LuksanVlcekBenchmark; path = "@lvb", sizes = 1000) — and the module method forwards to the Val form, which is the uniform handle across providers (it works for a package with no marker type of its own). No select helper: providers filter their own model lists.

The library says what it carriescnlp_nmodels / cnlp_model_name, the ABI's only fixed-name symbols, because every per-model entry point needs a prefix and this is where a prefix comes from.

Every model says what it instantiates fromP_argtype publishes a typed signature, "type|description,…": "" for a fixed model, "int|size", "string|…" for an argument-function model, or a structured model's full field list ("int|arg1,Vector{f64}|v0,Table{i::int w::f64 s::f64}|bus"). Every model answers — including those with no builder schema — and the signature identifies the entry point without symbol probing.

Consumer counterparts are already on both masters (CNLPModels.jl d4070e5, cnlpmodels-py 45d3f08): available_models, argtype, schema answering rather than throwing.

Gate on this exact tip: 242/242, including signature assertions for all four instantiation surfaces; compile_all end-to-end verified against LuksanVlcekBenchmark's recipes branch (two models compiled into one library, exact at a size never compiled).

🤖 Generated with Claude Code

sshin23 and others added 3 commits August 14, 2026 06:39
ExaModelsCompiler declares the verb and owns its keyword contract —
path, only, exclude, and whatever compile_library takes; a package that
ships models implements the Val form for itself in an extension, so
providing models never costs a dependency on a compiler toolchain.
Callers name the package: compile_all(LuksanVlcekBenchmark; path =
"@lvb"), which forwards to the Val method.

select(models; only, exclude) is the shared half, so every provider
offers the same filtering and a typo is refused rather than quietly
compiling a library with a model missing.

Verified against LuksanVlcekBenchmark on its recipes branch: a draft
extension over its eighteen models compiled two of them at a size never
compiled, exact against the in-Julia models on objective and gradient,
with an unknown name in only= refused before any compiling. Suite 246.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A two-line filter does not earn hub API. The docstring's worked example
no longer advertises only/exclude as a shared contract; a provider that
wants filtering writes it in its own extension, as the LVB draft does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt signatures

Two additions to the emitted ABI, both answering questions a caller
holding only a library path has no way to ask today.

cnlp_nmodels / cnlp_model_name — the models a library carries, by name.
Library-level and fixed-name, the only such symbols: every per-model
entry point needs a prefix, and these are where a prefix comes from.

P_argtype — what a model instantiates FROM, as a signature rather than
an enum: comma-separated types, each optionally type|description
("int|size", "string|...", or a structured model's full field list).
Every model answers, including those with no builder schema, and the
signature also identifies the entry point without symbol probing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Your PR requires formatting changes to meet the project's style guidelines.

Please run:

julia --project=@runic -e 'using Pkg; Pkg.add("Runic")'
julia --project=@runic -e "using Runic; exit(Runic.main(ARGS))" -- --fix <files>

(or git runic main if you have the git wrapper installed)

Note: the full diff is omitted because it can exceed GitHub Actions input limits.

The C interface ExaModelsCompiler emits is now independently specified
— cnlp.h at madsuite-org/cnlp-abi, tag v0.1, with both consumers
conforming and citing the same version — so the producer says so too:
the module docstring and the README name the ABI and pin the tag,
closing the loop where every package in the constellation references
one versioned contract instead of describing its own copy of it. Four
stale MadNLP-org links corrected to madsuite-org on the way through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sshin23

sshin23 commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

One commit added: the emitted library's contract now has a name — the module docstring and README cite cnlp ABI v0.1 (madsuite-org/cnlp-abi, the header-is-the-spec repo, tagged) — plus four stale MadNLP-org links corrected. Both consumers already conform to and cite the same tag on their masters, with the spec header vendored into their fixtures so signature drift fails compilation (which caught a real char*/uint8_t* drift on first compile). Gate on the tip: 242/242.

With this, merging #313 completes the constellation on ExaModels' side: producer, spec, and both consumers referencing one versioned contract. LVB #4, COPS #16 and ExaModelsPower #57 remain the benchmark-side pieces.

🤖 Generated with Claude Code

ExaModels relates to NLPModels.jl; the subpackage that emits the ABI is
the one that names it. The README bullet keeps its corrected links.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results


Relative timing: current / main  (values < 1.0 are improvements)

================================================================================
  backend-instance-param          |      obj     cons     grad      jac     hess
================================================================================
  CUDA-OPF-case1354               |    0.969    0.972    0.879    1.015    0.990
  CUDA-OPF-case14                 |    1.022    0.983    1.009    0.994    1.003
  CUDA-OPF-case30000              |    1.019    0.967    1.024    0.998    0.991
  CUDA-chain-10                   |    1.013    1.029    0.960    1.024    1.030
  CUDA-chain-100                  |    1.015    1.035    1.034    1.026    1.027
  CUDA-chain-1000                 |    1.045    1.031    1.082    1.025    1.031
  CUDA-elec-10                    |    1.018    1.014    1.026    0.969    1.022
  CUDA-elec-100                   |    1.070    1.016    1.026    1.031    0.947
  CUDA-elec-1000                  |    0.974    1.017    1.018    1.029    1.613
  CUDA-rosenrock-1000             |    0.972    1.041    0.994    1.029    1.040
  CUDA-rosenrock-10000            |    1.006    1.049    1.013    1.025    1.036
  CUDA-rosenrock-100000           |    0.955    1.043    1.002    1.025    1.033
--------------------------------------------------------------------------------
  AMDGPU-OPF-case1354             |    0.986    0.966    0.933    0.976    1.013
  AMDGPU-OPF-case14               |    0.995    0.989    0.962    1.010    1.000
  AMDGPU-OPF-case30000            |    1.031    0.988    0.967    0.965    0.957
  AMDGPU-chain-10                 |    1.003    0.957    0.953    0.924    0.949
  AMDGPU-chain-100                |    0.989    0.988    0.933    0.991    0.952
  AMDGPU-chain-1000               |    1.001    0.981    0.917    0.966    1.007
  AMDGPU-elec-10                  |    0.996    0.934    0.935    0.906    0.916
  AMDGPU-elec-100                 |    0.998    0.914    0.934    0.986    0.987
  AMDGPU-elec-1000                |    1.110    0.938    1.776    0.994    1.297
  AMDGPU-rosenrock-1000           |    0.964    0.964    0.964    1.001    0.964
  AMDGPU-rosenrock-10000          |    0.983    0.984    0.969    1.007    0.968
  AMDGPU-rosenrock-100000         |    1.042    0.973    0.961    0.977    1.183
--------------------------------------------------------------------------------
  oneAPI-OPF-case1354             |    1.010    1.101    0.674    1.009    0.689
  oneAPI-OPF-case14               |    0.940    1.364    1.165    0.077    0.438
  oneAPI-OPF-case30000            |    0.872    0.388    1.613    0.469    1.023
  oneAPI-chain-10                 |    1.038    0.867    0.238    0.168    0.160
  oneAPI-chain-100                |    0.151    2.980    0.063    0.735    1.147
  oneAPI-chain-1000               |    1.379    0.712    0.463    0.044    0.094
  oneAPI-elec-10                  |    0.266    0.543    0.573    0.690    0.863
  oneAPI-elec-100                 |    1.321    0.727    0.503    0.332    0.639
  oneAPI-elec-1000                |    1.070    1.412    0.743    0.411    0.584
  oneAPI-rosenrock-1000           |    0.892    2.065    2.662    2.028    0.261
  oneAPI-rosenrock-10000          |    0.734    4.796    1.398    0.118    1.952
  oneAPI-rosenrock-100000         |    0.449    1.024    1.330    1.417    2.044
--------------------------------------------------------------------------------
  nothing-OPF-case1354            |    0.995    1.017    1.084    0.919    1.283
  nothing-OPF-case14              |    1.078    0.776    0.960    1.083    0.998
  nothing-OPF-case30000           |    0.994    1.121    1.005    0.979    0.949
  nothing-chain-10                |    0.758    0.991    1.015    1.003    1.529
  nothing-chain-100               |    1.022    1.039    0.973    0.966    1.682
  nothing-chain-1000              |    0.557    1.047    0.995    0.931    1.116
  nothing-elec-10                 |    1.022    0.948    1.034    1.030    1.004
  nothing-elec-100                |    1.009    1.014    1.005    1.072    1.008
  nothing-elec-1000               |    1.004    0.851    1.024    1.000    0.833
  nothing-rosenrock-1000          |    0.998    1.342    1.029    0.688    1.421
  nothing-rosenrock-10000         |    0.984    1.308    1.561    1.113    1.152
  nothing-rosenrock-100000        |    1.009    1.003    1.202    1.006    0.761
================================================================================

sshin23 and others added 4 commits August 14, 2026 07:48
The catalogue was emitted but never asserted on a real library — the
consumers only exercised their reading against the hand-written
fixture. The five-model library's testset now reads available_models
through the consumer from the compiled artifact, closing the loop from
compile_library to a caller discovering every model with only a path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Removed on the judgment that a two-line filter does not earn hub API;
restored on evidence: all three provider extensions written since (LVB,
COPS, ExaModelsPower) independently reimplemented the refusal half with
near-identical messages, because a silent drop produces a library
correct in every respect except the model you asked for — an absence
that surfaces at the consumer, far from the typo. Restored byte-for-
byte from the commit that introduced it, tests included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A constraint or objective may iterate a collection of data points
(for (i, tau) in nodes), and that collection then forms one axis of
the block's size field. size/total had methods for Int and range axes
and none for an array's, so everything downstream threw a MethodError:
ExaModels' own multipliers on such a block, and — where it was found —
a compiled library's layout query, which degraded the throw to status
2 and left six of COPS's seventeen models unconstructable through the
consumer while compiling clean and cataloguing correctly. channel
passes with 3-D deferred blocks (all range axes) while catmix fails
(collocation-node vector axis), which is what closed the correlation
its finder could not.

One method: _length(n::AbstractArray) = length(n). Verified on the
reproducer — catmix compiles, instantiates, c1 reads dims (20, 3, 2)
matching its iterator's shape, objective exact against in-Julia — and
regression-tested at both layers: a data-axis constraint in the
feature suite, and a compiled data-axis block in the layout testset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ve contact

select's docstring now states the two shapes misread on first contact
by the providers adopting it — it takes the assembled PAIRS (build all
recipes, filter after: recipes are cheap, compiles are not), and
exclude defaults to (), not nothing.

Deliberately absent: the frozen-dimension validation convention drafted
for compile_all's example. Its own author falsified it before it
shipped — a named package function cannot carry the frozen value, and
a closure that could is refused by the reachability guard — so the
enforcement design (parameterized callable argfuns, or an
instantiation-time check) is tracked on the PR instead of prescribed
in a docstring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sshin23

sshin23 commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

Final state of the branch, after two rounds of field testing by the benchmark providers:

  • 7383f516the data-axis fix: _length(::AbstractArray) in ExaModels. A constraint iterating a data collection (catmix's collocation nodes) forms an array axis in its block's size field; size/total/multipliers threw on it, and the compiled layout query degraded that to status 2, leaving six of COPS's seventeen models unconstructable through the consumer. Verified on the reproducer (catmix instantiates, dims (20,3,2), objective exact vs in-Julia); regression tests at both layers. Gates: ExaModels 1621/0/2, EMC 252/252.
  • docs commitselect's docstring carries the two shapes both adopting providers misread (pairs not names; exclude = ()).

Open design item, deliberately NOT in the docs (its author falsified the drafted guidance before it shipped): a recipe that bakes a dimension (multi-period OPF's bus count) produces a library that silently accepts mismatched inputs — a named argfun cannot carry the frozen value, and a closure that could is refused by the reachability guard. Candidate fixes: admit parameterized callable types as argfuns (Pkg.MPArgs{14}() is statically nameable), or check at instantiation with the baked value carried in the core. Neither is in this PR; disclosure via the argtype description is the interim mitigation under discussion.

Field verification on this branch: emp compiles clean on the tip (188s, 0 verifier errors, three prefixes); COPS's closing 17-model run is in flight.

🤖 Generated with Claude Code

@sshin23
sshin23 merged commit a9fbf9c into main Aug 14, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant