Commit edd4720
authored
* feat: add vendored C++ kmeans1d compiled via torch cpp_extension
Vendor coremltools' kmeans1d C++ core (_core.cpp, byte-for-byte from apple/coremltools; upstream kmeans1d 0.3.1, MIT) and JIT-compile it at runtime with torch.utils.cpp_extension, invoking its extern "C" entry points via ctypes. Provides cluster()/Clustered as a drop-in replacement for coremltools._deps._kmeans1d, with fail-loud input validation.
* refactor: switch palettization kmeans to vendored kmeans1d
Point _efficient_kmeans and kmeans_fake_palettize at coreai_opt._utils._kmeans1d instead of coremltools._deps._kmeans1d. The cluster()/Clustered call contract is unchanged.
* refactor: make coreai model palettization use vendored kmeans1d
Drop the optional third-party kmeans1d import and _HAS_KMEANS1D flag in palettize_utils; import coreai_opt._utils._kmeans1d unconditionally. The sklearn fallback for vector k-means is unchanged.
* test: add equivalence and source-hash tests for vendored kmeans1d
Compare cluster() output against the coremltools oracle (unweighted, weighted, large-n, collapse, fp16 ties), assert fixed known-good cases and input validation, and add a canary that hard-fails if the vendored _core.cpp drifts from apple/coremltools main.
* build: move coremltools to optional coreml dependency
coremltools is no longer needed at runtime; move it (and its numpy<2.4 cap) into a coreml optional-dependency + self-referencing dependency-group, kept in default-groups so the test oracle is available. Ship the vendored _core.cpp and LICENSE as package data.
* docs: add changelog fragment for coremltools optional dependency
* perf: compile vendored kmeans1d with -O2 -DNDEBUG to match coremltools
The vendored kmeans1d C++ extension was 10-15x slower per call than
coremltools' precompiled version (measured directly: ~0.9s vs ~0.06s
on a realistic-size weighted cluster() call), even with the actual
JIT-compile lock/cache fully warmed. This was the real cause of the
1.2x-2.6x palettization wall-clock regression seen in an end-to-end
LLM eval, not JIT-compile lock contention across parallel workers
(a prior "warmup()" fix targeting that, since reverted, made no
measurable difference).
Root cause: torch.utils.cpp_extension.load() does not inherit
CPython's sysconfig OPT/CFLAGS the way distutils/setuptools does, so
it defaults to -O0. coremltools' setup.py sets no explicit -O flag
either, but distutils prepends sysconfig's "-O2 -DNDEBUG" (the
standard python.org/conda default) ahead of its extra_compile_args,
so its shipped .so is effectively an -O2 build. This tight-inner-loop,
template-heavy DP algorithm is extremely sensitive to that gap.
Pass the same flags explicitly so torch's JIT build matches
coremltools' effective compile line. Numeric output is unaffected
(verified against the existing coremltools-oracle equivalence tests).
* fix: add setuptools as runtime dependency for vendored kmeans1d
torch.utils.cpp_extension.load() imports setuptools internally to JIT-compile
the vendored kmeans1d core. This was previously pulled in transitively via
coremltools, which is now an optional dependency, so it must be declared
explicitly.
* refactor: move vendored kmeans1d into src/coreai_opt/deps/_kmeans1d
Isolates the vendored third-party kmeans1d source (and its MIT LICENSE)
into a dedicated deps/ folder, separate from coreai-opt's own internal
_utils modules, per Apple legal's guidance on incorporating vendored
dependencies (mirrors how coremltools itself vendored the same code).
* refactor: update import paths for moved kmeans1d deps folder
Completes the previous move: repoints pyproject.toml package-data and
the three call sites at coreai_opt.deps._kmeans1d instead of the old
coreai_opt._utils._kmeans1d location. Also anchors the .gitignore
deps/ rule to the repo root (/deps/) so it stops shadowing the new
src/coreai_opt/deps/ package.
* docs: apply Apple copyright line to vendored kmeans1d headers
Bring the per-file headers in line with Apple legal's guidance for
incorporating vendored third-party code (mirrors coremltools' own
kmeans1d headers): every file in the vendored package now carries the
upstream MIT license text followed by a trailing Apple copyright line,
with no coreai-opt repo-wide header layered on top since this isn't
coreai-opt's own code. Bumps the trailing line from "Copyright (c) 2023
Apple Inc." (when coremltools first vendored it) to "Copyright (c) 2026
Apple Inc." (when coreai-opt vendored it from coremltools).
Also excludes src/coreai_opt/deps/ from the add-license-header
pre-commit hook, which otherwise unconditionally stamps coreai-opt's
own BSD-3 header onto any .py file lacking one.
* docs: add NOTICE.txt for kmeans1d attribution
Attributes the vendored kmeans1d source under src/coreai_opt/deps/ to
its upstream MIT license, per Apple legal's guidance for incorporating
vendored dependencies (mirrors the equivalent NOTICE.txt commit in
coremltools, which vendored the same code).
* test: normalize Apple copyright year in kmeans1d drift canary
The vendored-source-matches-upstream canary hashes our _core.cpp
against coremltools' live upstream copy. Now that coreai-opt's copy
carries its own vendoring-year copyright line ("Copyright (c) 2026
Apple Inc.") rather than coremltools' ("Copyright (c) 2023 Apple
Inc."), the raw byte hashes never match. Strip that one known,
intentional line from both sides before hashing so the canary still
catches real drift in the kmeans1d logic without permanently failing
on the copyright year.
* chore: remove unused deps/ gitignore rule
The unanchored `deps/` rule (from the initial commit, alongside other
personal-scratch entries like scratch/ and /local_stash) silently
shadowed the newly-added, tracked src/coreai_opt/deps/ package: search
tools like ripgrep skip gitignored paths by default, and any future
new file added under deps/ would need an explicit force-add. Nothing
in the repo (Makefile, CI, scripts, docs) reads or writes a deps/
folder, so remove the rule outright rather than just anchoring it.
* docs: note C++ compiler requirement in changelog fragment
The vendored kmeans1d is JIT-compiled via torch.utils.cpp_extension,
which needs a C++ toolchain on the host at runtime; previously this
requirement rode in transitively via the coremltools wheel, so it's
worth calling out now that coremltools is optional.
* docs: trim changelog wording per review feedback
Drop "(copied byte-for-byte from coremltools)" -- attribution already
lives in the vendored files' headers and NOTICE.txt, and the phrase
will go stale once the core is further optimized (per aseemw's PR #31
review).
* docs: drop coremltools-diff comment from vendored core.py
aseemw's review: comments explaining implementation choices by diffing
against what coremltools' build does are unnecessary now that this is
a fresh adaptation, not a diff against coremltools. Drop the comment
entirely rather than rephrasing it to be self-contained; the flags
themselves are unchanged.
* build: drop unneeded coreml group from docs dependencies
Re-verified via explain-coreai-opt (aseemw asked why docs needs the
coreml group): Sphinx's autodoc/autosummary machinery does genuinely
import every public coreai_opt module at build time, but nothing under
src/coreai_opt/ imports coremltools anywhere -- the only remaining
kmeans1d/coremltools attribution now lives in comments and the vendored
package's own LICENSE/header, none of which autodoc touches. No CI job
builds docs either, so this has no build-time effect to verify against.
* test: remove coremltools as a test dependency for kmeans1d
aseemw's PR #31 review: kmeans1d correctness tests importing
coremltools as a live oracle defeats the purpose of removing it as a
required dependency, and the "must stay byte-identical to upstream"
canary asserts an invariant that isn't actually required (the vendored
code can be modified/adapted going forward).
Collapses TestEquivalence/TestKmeans1DBehavior/TestsKmeans1dFromCoremltools
into one TestKmeans1D class and deletes TestVendoredSourceMatchesUpstream
entirely. Small discrete-input cases (k>n clamping, fewer-than-k-cluster
collapse, explicit duplicates) now assert hardcoded literal values
directly, captured by running the vendored cluster() once (already
validated against coremltools in CI, so a known-good golden value).
Larger/random-array cases (unweighted/weighted equivalence grids,
large-n up to 100k, fp16 duplicates, weighted-integer-counts) would
require thousands of literal values to hardcode inline, so their
inputs and expected coremltools-oracle output are instead checked into
tests/palettization/assets/kmeans1d/*.npz and loaded at test time
(~3.3MB total, mostly the large-n case).
Net effect: this test file has zero coremltools imports (confirmed via
AST inspection), so coreml stops being an implicit test dependency for
kmeans1d correctness.
1 parent 1001e57 commit edd4720
17 files changed
Lines changed: 787 additions & 31 deletions
File tree
- changelog.d
- src/coreai_opt
- coreai_utils/_utils
- deps/_kmeans1d
- palettization/kmeans
- tests/palettization
- assets/kmeans1d
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
51 | 50 | | |
52 | 51 | | |
53 | 52 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
35 | | - | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
36 | 38 | | |
| 39 | + | |
37 | 40 | | |
38 | 41 | | |
| 42 | + | |
| 43 | + | |
39 | 44 | | |
40 | 45 | | |
41 | 46 | | |
| |||
54 | 59 | | |
55 | 60 | | |
56 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
57 | 66 | | |
58 | 67 | | |
59 | 68 | | |
| |||
95 | 104 | | |
96 | 105 | | |
97 | 106 | | |
| 107 | + | |
98 | 108 | | |
| 109 | + | |
99 | 110 | | |
100 | 111 | | |
101 | 112 | | |
| |||
145 | 156 | | |
146 | 157 | | |
147 | 158 | | |
| 159 | + | |
148 | 160 | | |
149 | 161 | | |
150 | 162 | | |
| |||
153 | 165 | | |
154 | 166 | | |
155 | 167 | | |
156 | | - | |
| 168 | + | |
157 | 169 | | |
158 | 170 | | |
159 | 171 | | |
160 | 172 | | |
161 | 173 | | |
162 | 174 | | |
163 | | - | |
| 175 | + | |
164 | 176 | | |
165 | 177 | | |
166 | 178 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | 33 | | |
41 | 34 | | |
42 | 35 | | |
| |||
99 | 92 | | |
100 | 93 | | |
101 | 94 | | |
102 | | - | |
103 | | - | |
| 95 | + | |
104 | 96 | | |
105 | 97 | | |
106 | 98 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
0 commit comments