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.