lua-agent-lab / verify_cache.lua
jeorgexyz's picture
Publish reproducible Lua agent experiments and traces
98acb70 verified
Raw History Blame Contribute Delete
3.46 kB
-- verify_cache.lua - The KV cache reuse invariant.
--
-- lua54 verify_cache.lua [--checkpoint ../lua_llama/stories15M.bin]
--
-- backend/local.lua does two things that are only safe if stale cache
-- entries are truly unreachable:
--
-- 1. `feed` rewinds `pos` on a prefix divergence instead of clearing the
-- cache past that point.
-- 2. `reset` allocates the cache once and afterwards only zeroes `pos`,
-- rather than re-zeroing 8.4M numbers per task.
--
-- Both rest on the same claim: the cache is position-indexed, and every slot
-- below `pos` is written before it is read again. If that is wrong, output
-- silently degrades rather than crashing -- the worst failure mode there is.
--
-- So this checks it the way repo 1 checked its speculative draft path: at
-- temperature 0, a warm run and a cold run must produce byte-identical text.
-- A mismatch here is the bug that KV-cache reuse always has.
package.path = "./?.lua;" .. package.path
local tools = require('tools')
local protocol = require('protocol')
local blocal = require('backend.local')
local opts = {}
for i = 1, #arg, 2 do opts[(arg[i]:gsub("^%-%-", ""))] = arg[i + 1] end
local reg = tools.registry()
reg:add(tools.calc)
reg:add(tools.read_file)
local PROMPTS = {
protocol.render_compact(reg, "What is 4871 * 209?"),
protocol.render_compact(reg, "Read the notes file."),
protocol.render_compact(reg, "Add 17 and 25."),
}
local function new_backend()
return blocal.new({ tools = reg, constrain = true, temperature = 0.0,
checkpoint = opts.checkpoint, llama_path = opts.llama_path })
end
io.write("loading...\n") io.flush()
local warm = new_backend()
-- Warm: one backend, all prompts in sequence, reset between them. This
-- exercises both the allocate-once reset and the prefix rewind in feed.
local warm_out = {}
for i, p in ipairs(PROMPTS) do
warm:reset()
warm_out[i] = warm:complete(p, {})
io.write(string.format(" warm %d done\n", i)) io.flush()
end
-- Interleaved: same backend, prompts in a different order, then the originals
-- again. If a stale entry were reachable, a differing history would surface it.
warm:reset() warm:complete(PROMPTS[3], {})
warm:reset() warm:complete(PROMPTS[2], {})
local interleaved = {}
for i, p in ipairs(PROMPTS) do
warm:reset()
interleaved[i] = warm:complete(p, {})
end
-- Cold: a brand-new backend per prompt. The ground truth.
local cold_out = {}
for i, p in ipairs(PROMPTS) do
local fresh = new_backend()
cold_out[i] = fresh:complete(p, {})
io.write(string.format(" cold %d done\n", i)) io.flush()
end
local fail = 0
io.write("\n")
for i = 1, #PROMPTS do
local w_ok = warm_out[i] == cold_out[i]
local x_ok = interleaved[i] == cold_out[i]
if not w_ok or not x_ok then fail = fail + 1 end
io.write(string.format("prompt %d: warm==cold %s interleaved==cold %s\n",
i, w_ok and "yes" or "NO", x_ok and "yes" or "NO"))
if not (w_ok and x_ok) then
io.write(" cold: " .. cold_out[i]:gsub("\n", " | ") .. "\n")
io.write(" warm: " .. warm_out[i]:gsub("\n", " | ") .. "\n")
io.write(" interleaved: " .. interleaved[i]:gsub("\n", " | ") .. "\n")
end
end
io.write("\n")
if fail == 0 then
io.write("PASS: cache reuse is transparent at temperature 0\n")
else
io.write(string.format("FAIL: %d of %d prompts diverged\n", fail, #PROMPTS))
end
os.exit(fail == 0 and 0 or 1)