File size: 3,463 Bytes
98acb70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
-- 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)