|
39 | 39 | ) |
40 | 40 | from aie.iron.controlflow import range_ |
41 | 41 | from aie.utils.npukernel import NPUKernel |
| 42 | +from aie.utils.hostruntime.hrxruntime.hostruntime import CachedHRXRuntime |
42 | 43 |
|
43 | 44 | _TILE = 16 |
44 | 45 | _SIZE = 1024 |
@@ -76,6 +77,38 @@ def add_one(input_buf: In, output_buf: Out, *, N: CompileTime[int]): |
76 | 77 | return _add_one_design(input_buf, output_buf, N=N) |
77 | 78 |
|
78 | 79 |
|
| 80 | +def _add_two_design(input_buf: In, output_buf: Out, N: CompileTime[int]): |
| 81 | + """Add 2 to every element -- a second, distinct executable (see add_one).""" |
| 82 | + tile_ty = np.ndarray[(_TILE,), np.dtype[np.int32]] |
| 83 | + tensor_ty = np.ndarray[(N,), np.dtype[np.int32]] |
| 84 | + |
| 85 | + of_in = ObjectFifo(tile_ty, name="in") |
| 86 | + of_out = ObjectFifo(tile_ty, name="out") |
| 87 | + |
| 88 | + def core_body(of_in, of_out): |
| 89 | + for _ in range_(N // _TILE): |
| 90 | + elem_in = of_in.acquire(1) |
| 91 | + elem_out = of_out.acquire(1) |
| 92 | + for i in range_(_TILE): |
| 93 | + elem_out[i] = elem_in[i] + 2 |
| 94 | + of_in.release(1) |
| 95 | + of_out.release(1) |
| 96 | + |
| 97 | + worker = Worker(core_body, fn_args=[of_in.cons(), of_out.prod()]) |
| 98 | + |
| 99 | + def sequence(a, b, in_h, out_h): |
| 100 | + in_h.fill(a) |
| 101 | + out_h.drain(b, wait=True) |
| 102 | + |
| 103 | + rt = Runtime(sequence, [tensor_ty, tensor_ty, of_in.prod(), of_out.cons()]) |
| 104 | + return Program(iron.get_current_device(), rt, workers=[worker]).resolve_program() |
| 105 | + |
| 106 | + |
| 107 | +@compileconfig |
| 108 | +def add_two(input_buf: In, output_buf: Out, *, N: CompileTime[int]): |
| 109 | + return _add_two_design(input_buf, output_buf, N=N) |
| 110 | + |
| 111 | + |
79 | 112 | def _hrx_runtime(): |
80 | 113 | """The default NPU runtime, which is the HRX runtime under NPU_RUNTIME=hrx. |
81 | 114 |
|
@@ -143,3 +176,49 @@ def test_deep_chain(hrx_kernel): |
143 | 176 | for k, st in enumerate(stages): |
144 | 177 | st.to("cpu") |
145 | 178 | np.testing.assert_array_equal(st.numpy(), base + (k + 1)) |
| 179 | + |
| 180 | + |
| 181 | +def test_chain_survives_executable_eviction(): |
| 182 | + """A run_chain handle must outlive eviction of its executable cache entry. |
| 183 | +
|
| 184 | + The executable cache holds a single libhrx reference per executable and |
| 185 | + drops it on LRU eviction. A chain keeps every step's handle live for the |
| 186 | + whole batched dispatch, so if a later load evicts an earlier step's entry |
| 187 | + the handle must still own the executable -- otherwise the dispatch touches a |
| 188 | + freed executable (hrx_stream_dispatch: base_executable == NULL). We force |
| 189 | + the eviction by shrinking the cache to one entry and loading two distinct |
| 190 | + executables, then chaining across both. Without the per-handle retain this |
| 191 | + dispatches a freed executable and fails; with it the chain runs correctly. |
| 192 | +
|
| 193 | + Uses a fresh ``CachedHRXRuntime`` rather than the process-wide |
| 194 | + ``DefaultNPURuntime`` singleton so the cache starts empty and eviction is |
| 195 | + deterministic (a shared runtime could already hold entries, making the load |
| 196 | + a cache hit or evicting an unrelated entry). |
| 197 | + """ |
| 198 | + rt = CachedHRXRuntime() |
| 199 | + |
| 200 | + xa, ia = add_one.specialize(N=_SIZE).compile() |
| 201 | + xb, ib = add_two.specialize(N=_SIZE).compile() |
| 202 | + ka = NPUKernel(str(xa), str(ia), kernel_name="MLIR_AIE") |
| 203 | + kb = NPUKernel(str(xb), str(ib), kernel_name="MLIR_AIE") |
| 204 | + |
| 205 | + rt._cache_size = 1 |
| 206 | + try: |
| 207 | + h_add1 = rt.load(ka) # cache: {add_one} |
| 208 | + h_add2 = rt.load(kb) # size==1 -> evicts + releases add_one's executable |
| 209 | + # h_add1 now references an executable the cache no longer keeps alive. |
| 210 | + |
| 211 | + base = np.arange(1, _SIZE + 1, dtype=np.int32) |
| 212 | + a1 = iron.tensor(base, dtype=np.int32, device="npu") |
| 213 | + c1 = iron.zeros(_SIZE, dtype=np.int32, device="npu") |
| 214 | + a2 = iron.tensor(base, dtype=np.int32, device="npu") |
| 215 | + c2 = iron.zeros(_SIZE, dtype=np.int32, device="npu") |
| 216 | + |
| 217 | + rt.run_chain([(h_add1, [a1, c1]), (h_add2, [a2, c2])]) |
| 218 | + |
| 219 | + c1.to("cpu") |
| 220 | + c2.to("cpu") |
| 221 | + np.testing.assert_array_equal(c1.numpy(), base + 1) |
| 222 | + np.testing.assert_array_equal(c2.numpy(), base + 2) |
| 223 | + finally: |
| 224 | + rt.cleanup() |
0 commit comments