Skip to content

Commit 42f8a69

Browse files
committed
perf: warm up vendored kmeans1d JIT compile before spawning workers
Palettization spawns many worker processes that each call _kmeans1d.cluster(). Since the vendored extension JIT-compiles on first use per-process, every worker independently raced the same on-disk build lock: one worker compiled while the rest spin-waited for it instead of just loading the finished .so, serializing a one-time multi-minute compile across the critical path. Add _kmeans1d.warmup() and call it once in the parent process before each worker pool is created (KMeansPalettizer and the coreai-torch palettize_utils compress pool), so every worker's load() call finds the extension already built.
1 parent a17f5c0 commit 42f8a69

5 files changed

Lines changed: 32 additions & 3 deletions

File tree

src/coreai_opt/_utils/_kmeans1d/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
# Use of this source code is governed by a BSD-3-Clause license that can
44
# be found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause
55

6-
from coreai_opt._utils._kmeans1d.core import Clustered, cluster
6+
from coreai_opt._utils._kmeans1d.core import Clustered, cluster, warmup
77

8-
__all__ = ["Clustered", "cluster"]
8+
__all__ = ["Clustered", "cluster", "warmup"]

src/coreai_opt/_utils/_kmeans1d/core.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ def _dll():
6060
return _DLL
6161

6262

63+
def warmup() -> None:
64+
"""Force the JIT-compiled extension to build once in the current process.
65+
66+
Call this in a parent process before spawning workers that call
67+
cluster(); each spawned worker's own first-use compile would otherwise
68+
race the same on-disk build lock and block on the others' compile time.
69+
"""
70+
_dll()
71+
72+
6373
def cluster(array: Sequence[float], k: int, *, weights: Sequence[float] | None = None) -> Clustered:
6474
"""
6575
:param array: A sequence of floats

src/coreai_opt/coreai_utils/_utils/palettize_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ def _grouped_channelwise_compress(
256256

257257
if mode == "KMEANS" and num_kmeans_workers > 1:
258258
if _compress_pool is None:
259+
# Warm up the JIT-compiled kmeans1d extension here first: each
260+
# worker's own first-use compile would otherwise race the same
261+
# on-disk build lock and block on the others' compile time
262+
# instead of just loading the built artifact.
263+
_kmeans1d.warmup()
259264
_compress_pool = Pool(processes=num_kmeans_workers)
260265
atexit.register(lambda: _compress_pool.terminate())
261266
lut, indices = zip(

src/coreai_opt/palettization/kmeans/palettizer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import torch.nn.utils.parametrize as P
1717
from tqdm import tqdm
1818

19+
from coreai_opt._utils import _kmeans1d
1920
from coreai_opt._utils.eager_utils import (
2021
EagerCompressionComponentBuilderMixin as _EagerCompressionComponentBuilderMixin,
2122
)
@@ -501,7 +502,11 @@ def _calculate_centroids_parallel(self, num_workers: int) -> None:
501502
)
502503

503504
# spawn (not fork) so workers don't inherit the parent's CUDA context
504-
# or other process-global state.
505+
# or other process-global state. Warm up the JIT-compiled kmeans1d
506+
# extension here first: each worker's own first-use compile would
507+
# otherwise race the same on-disk build lock and block on the
508+
# others' compile time instead of just loading the built artifact.
509+
_kmeans1d.warmup()
505510
pool_args = [(info.fp_module, info.weight, info.layer_name) for info in fp_info]
506511
ctx = mp.get_context("spawn")
507512
with ctx.Pool(processes=effective_workers) as pool:

tests/test_utils/test_kmeans1d.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ def test_centroids_are_ascending_and_labels_in_range(self):
164164
assert clusters.shape == array.shape
165165
assert centroids.dtype == np.float64
166166

167+
def test_warmup_then_cluster_still_works(self):
168+
# warmup() must be safe to call (including redundantly, since _dll()
169+
# short-circuits once built) and must not affect subsequent results.
170+
# k=1 is unambiguous: the single centroid is just the mean.
171+
_kmeans1d.warmup()
172+
_kmeans1d.warmup()
173+
result = _kmeans1d.cluster([1.0, 5.0, 9.0], 1)
174+
np.testing.assert_allclose(result.centroids, [5.0], rtol=_RTOL, atol=_ATOL)
175+
167176
def test_accepts_list_and_preserves_original_order(self):
168177
# A plain Python list (the doc-script call style) must work, and labels are
169178
# returned in the original (unsorted) order.

0 commit comments

Comments
 (0)