[Caching] Ref 7: CPU per-task artifact cache (ORC object serialize/load) - #901
Open
hughperkins wants to merge 22 commits into
Open
[Caching] Ref 7: CPU per-task artifact cache (ORC object serialize/load)#901hughperkins wants to merge 22 commits into
hughperkins wants to merge 22 commits into
GitHub Actions / Coverage Report
succeeded
Sep 3, 2026 in 0s
Diff Coverage Report
See details below for per-line coverage annotations.
Details
Coverage Report (69c895735)
| Metric | Value |
|---|---|
| Diff coverage (changed lines only) | 10% |
| Overall project coverage | 74% |
Total: 52 lines, 47 missing, 10% covered
🔴 tests/python/test_per_offload_cache.py (10%)
1292 # --- Cross-process per-task artifact cache (CUDA + AMDGPU + CPU) ------------------------------------------------------
1295 # task's own IR (name-free), so a later process reuses an unchanged task instead of recompiling it. CUDA and AMDGPU fill
1296 # it with GPU code and CPU with a host object; it is gated on `offline_cache`. Reuse is reported on
1297 # `PerOffloadCacheObservations.tasks_*` (-1 when offline_cache is disabled).
1375
1376
🟢 1377 @test_utils.test(arch=qd.cpu, offline_cache=False)
🟢 1378 def test_per_task_artifact_cache_disabled_without_offline_cache_cpu() -> None:
1379 # With offline_cache off the per-task cache never runs, so its counts stay at the -1 sentinel; the frontend split
1380 # still fires.
🔴 1381 @qd.kernel
🔴 1382 def kernel_two_loops(x: qd.types.ndarray(qd.f32, ndim=1)) -> None:
🔴 1383 for i in x:
🔴 1384 x[i] = x[i] * 2.0 + 1.0
🔴 1385 for i in x:
🔴 1386 x[i] = x[i] - 3.0
1387
🔴 1388 arr = qd.ndarray(qd.f32, shape=(_N,))
🔴 1389 arr.from_numpy(np.arange(_N, dtype=np.float32))
🔴 1390 kernel_two_loops(arr)
1391
🔴 1392 obs = kernel_two_loops._primal.per_offload_cache_observations
🔴 1393 assert obs.frontend_constructs_total == 2, obs
🔴 1394 assert obs.tasks_total == -1, obs
🔴 1395 assert obs.tasks_cache_hit == -1, obs
🔴 1396 assert obs.tasks_recompiled == -1, obs
🔴 1397 assert np.allclose(arr.to_numpy(), np.arange(_N) * 2.0 + 1.0 - 3.0), arr.to_numpy()
1398
1399
🟢 1400 def test_per_task_artifact_cache_reuses_shared_task_cross_process_cpu() -> None:
1401 # A fresh process with a warm disk cache loads an unchanged task instead of recompiling it. Re-init with the same
1402 # cache path emulates the second process.
🟢 1403 if qd.cpu not in test_utils.expected_archs():
🟢 1404 pytest.skip("this variant exercises the CPU per-task artifact cache")
1405
🔴 1406 cache_dir = tempfile.mkdtemp()
🔴 1407 try:
🔴 1408 qd.init(arch=qd.cpu, offline_cache=True, offline_cache_file_path=cache_dir)
1409
🔴 1410 @qd.kernel
🔴 1411 def k_first(x: qd.types.ndarray(qd.f32, ndim=1)) -> None:
🔴 1412 for i in x:
🔴 1413 x[i] = x[i] * 2.0 + 1.0
🔴 1414 for i in x:
🔴 1415 x[i] = x[i] - 3.0
1416
🔴 1417 a = qd.ndarray(qd.f32, shape=(_N,))
🔴 1418 a.from_numpy(np.arange(_N, dtype=np.float32))
🔴 1419 k_first(a)
🔴 1420 obs1 = k_first._primal.per_offload_cache_observations
🔴 1421 assert obs1.tasks_total >= 2, obs1
🔴 1422 assert obs1.tasks_cache_hit == 0, obs1
1423
1424 # Second "process": fresh runtime, same disk. k_second's first loop matches k_first's, so that task is a hit.
🔴 1425 qd.init(arch=qd.cpu, offline_cache=True, offline_cache_file_path=cache_dir)
1426
🔴 1427 @qd.kernel
🔴 1428 def k_second(x: qd.types.ndarray(qd.f32, ndim=1)) -> None:
🔴 1429 for i in x:
🔴 1430 x[i] = x[i] * 2.0 + 1.0
🔴 1431 for i in x:
🔴 1432 x[i] = x[i] + 7.0
1433
🔴 1434 b = qd.ndarray(qd.f32, shape=(_N,))
🔴 1435 b.from_numpy(np.arange(_N, dtype=np.float32))
🔴 1436 k_second(b)
🔴 1437 obs2 = k_second._primal.per_offload_cache_observations
🔴 1438 assert obs2.tasks_cache_hit > 0, obs2
🔴 1439 assert obs2.tasks_recompiled >= 1, obs2
🔴 1440 assert obs2.tasks_cache_hit + obs2.tasks_recompiled == obs2.tasks_total, obs2
1441
🔴 1442 assert np.allclose(b.to_numpy(), np.arange(_N) * 2.0 + 1.0 + 7.0), b.to_numpy()
1443 finally:
🔴 1444 qd.reset()
🔴 1445 shutil.rmtree(cache_dir, ignore_errors=True)
Loading