The compile cache#
hawk saves every compiled kernel. Building the same kernel again copies it from the cache instead of recompiling it.
Mechanically: a compile is a pure function of its inputs — the same
source, backend, scalar mode (the host CPU profile or the device’s
own float handling; see installation) and compiler flags
always produce the same lookup key, and therefore the same cache slot.
hawk.compile is content-closure keyed: the key covers not just the
kernel’s own source but everything the compiler reports it depends on
(headers, flags), so a HIT never runs a compiler and a changed dependency
never serves a stale one.
import pathlib
import tempfile
import hawk
import hawk.artifact
from hawk import Mutable, Param, Scalar
from hawk.compile import cache_stats, reset_cache_stats
@hawk.kernel
def affine(x: Scalar, a: Param, y: Mutable[Scalar]):
y = a * x
A miss, then a hit#
Building the same kernel twice into two different output directories,
against the same cache_dir, compiles once.
cache_dir = tempfile.mkdtemp()
out_dir_1 = pathlib.Path(tempfile.mkdtemp())
out_dir_2 = pathlib.Path(tempfile.mkdtemp())
reset_cache_stats()
first = hawk.artifact.build(affine, out_dir_1, targets=("host",), cache_dir=cache_dir)
second = hawk.artifact.build(affine, out_dir_2, targets=("host",), cache_dir=cache_dir)
print("first build — cache hit:", first.entries["host"].hit,
f"({first.entries['host'].seconds:.3f}s)")
print("second build — cache hit:", second.entries["host"].hit,
f"({second.entries['host'].seconds:.3f}s)")
print(cache_stats())
first build — cache hit: False (4.855s)
second build — cache hit: True (0.000s)
{'hits': 1, 'misses': 1}
The second build’s compiled .so is byte-identical to the first’s — it is
copied out of the cache slot, not recompiled — which is why its reported
compile time is zero.
A changed kernel is a different key#
Change the arithmetic — even trivially — and the lookup key changes with it: a different source is a different compile, by construction, never a stale hit.
@hawk.kernel
def affine_scaled(x: Scalar, a: Param, y: Mutable[Scalar]):
y = 2.0 * a * x # one constant added: a different kernel body
out_dir_3 = pathlib.Path(tempfile.mkdtemp())
third = hawk.artifact.build(affine_scaled, out_dir_3, targets=("host",), cache_dir=cache_dir)
print("changed kernel — cache hit:", third.entries["host"].hit)
changed kernel — cache hit: False
Where the cache lives#
cache_dir=None (the default) resolves to
hawk.compile.default_cache_dir() — a persistent directory under the
user’s cache home, shared across processes. Passing an explicit
cache_dir= (as this page does, above) is how a reproducible docs
build, or a CI job, keeps its compiles isolated from a developer’s own
cache.