File size: 12,748 Bytes
bbb6388 | 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | #include "strata/core/conversation_cache.hpp"
#include <cstdio>
#include <cstdlib>
using namespace strata::core;
namespace {
int checks = 0;
void check(bool value, const char* description) {
++checks;
if (!value) { std::fprintf(stderr, "FAIL: %s\n", description); std::exit(1); }
}
SavedConversation image(std::initializer_list<int32_t> ids, bool cvec = true) {
SavedConversation s;
s.live.ids = ids;
s.live.gdn.resize(64, 7);
s.cvec = cvec;
return s;
}
}
int main() {
{
ConversationBuffer bytes;
const size_t first = ConversationBuffer::segment_bytes + 17;
const size_t peak = bytes.allocation_peak(first);
bytes.resize(first, 7);
check(bytes.bytes() <= peak, "segmented payload and directory fit admitted bytes");
uint8_t* original = nullptr;
bytes.visit(0, 1, [&](uint8_t* p, size_t, size_t) { original = p; return true; });
const size_t grown_peak = bytes.allocation_peak(first + 99);
bytes.resize(first + 99, 9);
bytes.visit(0, 1, [&](uint8_t* p, size_t, size_t) {
check(p == original, "appending preserves existing payload addresses"); return true;
});
check(bytes.bytes() <= grown_peak, "growth including transient directory fits admission");
std::vector<uint8_t> tail(116);
check(bytes.read(tail.data(), ConversationBuffer::segment_bytes, tail.size()), "read spans segment boundaries");
check(std::all_of(tail.begin(), tail.begin()+17, [](auto v){return v==7;}) &&
std::all_of(tail.begin()+17, tail.end(), [](auto v){return v==9;}), "growth preserves prefix and initializes only suffix");
auto copy = bytes;
bytes.resize(ConversationBuffer::segment_bytes + 10);
bytes.resize(first, 7); bytes.resize(first + 99, 9);
check(bytes == copy, "equality ignores differing segmentation after rewind and regrowth");
check(!bytes.read(tail.data(), bytes.size()-1, 2), "range check rejects a truncated payload");
check(bytes.allocation_peak(SIZE_MAX) == SIZE_MAX, "allocation estimate rejects overflow");
}
{
ConversationBuffer bytes;
for (size_t n=1;n<=4096;++n) {
const size_t peak = bytes.allocation_peak(n*17);
bytes.resize(n*17,7);
check(bytes.bytes() <= peak,"small append stays within the predicted allocation peak");
}
size_t segments = 0;
bytes.visit(0,bytes.size(),[&](const uint8_t*,size_t,size_t){++segments;return true;});
check(segments <= 4,"thousands of small turns do not create thousands of restore transfers");
}
{
ConversationKv layer;
layer.k.resize(400);
std::vector<ConversationKv> layers;
layers.push_back(std::move(layer));
const size_t retained = layers.capacity()*sizeof(ConversationKv) + layers[0].bytes();
const size_t parked = image({1,2,3}).bytes();
ConversationCache cache(retained + parked, 4);
check(cache.put(image({1,2,3})), "park before retaining active storage");
cache.retain(std::move(layers), 16);
check(cache.bytes() == retained + parked && cache.size() == 1, "active retained storage consumes bytes but no parked slot");
cache.limit_reuse(9); cache.limit_reuse(12);
auto reuse = cache.take_reuse();
check(reuse.unchanged_tokens == 9, "a later continuation cannot undo a rewind's dirty boundary");
check(cache.bytes() == parked, "taking retained buffers releases their budget accounting");
cache.retain(std::move(reuse.kv), 16);
check(!cache.can_fit(parked),"retained storage is counted when checking a capture without eviction");
check(cache.retained_bytes() == retained && cache.size() == 1 && cache.evictions() == 0,
"optional reuse admission does not evict or release anything");
check(!cache.can_fit(SIZE_MAX) && !cache.can_fit(1,SIZE_MAX),"non-mutating reservation rejects overflow");
check(cache.make_room(parked), "reservation can discard optional active buffers");
check(cache.retained_bytes() == 0 && cache.size() == 1 && cache.evictions() == 0,
"pressure drops retained storage before evicting parked conversations");
}
const std::vector<int64_t> a = {1, 2, 3, 4}, b = {9, 8, 7, 6};
{
ConversationCache cache(1024, 2);
check(cache.put(image({1, 2, 3})), "park A");
check(cache.put(image({9, 8, 7})), "park B");
auto match = cache.best(a, {}, true);
check(match.tokens == 3 && match.live, "A/B/A: recover A");
auto restored = cache.take(match.index);
check(restored.live.ids == std::vector<int32_t>({1, 2, 3}), "taking selected A preserves identity");
check(cache.size() == 1 && cache.best(b, {}, true).tokens == 3, "B remains parked");
check(cache.bytes() == image({9,8,7}).bytes(), "byte accounting after take");
check(cache.put(std::move(restored)), "park returned A as newest");
check(cache.put(image({5, 6})), "evict oldest by slot limit");
check(cache.best(b, {}, true).tokens == 0 && cache.best(a, {}, true).tokens == 3, "B evicted before A");
check(cache.evictions() == 1, "eviction counter");
}
{
auto s = image({1, 2, 3});
ConversationCheckpoint cp;
cp.ids = {1, 2};
s.checkpoints.push_back(cp);
ConversationCache cache(4096, 4);
cache.put(std::move(s));
auto match = cache.best(std::vector<int64_t>{1, 2, 9, 4}, {}, true);
check(match.tokens == 2 && !match.live, "edited suffix falls back to parked checkpoint");
check(cache.best(a, {}, true).tokens == 3, "live prefix beats shorter checkpoint");
check(cache.best(a, {}, false).tokens == 0, "steering mode is isolated");
check(cache.best(std::vector<int64_t>{1, 2}, {}, true).tokens == 0, "equal-length checkpoint cannot consume last token");
check(cache.best(std::vector<int64_t>{1}, {}, true).tokens == 0, "short prompt cannot match");
check(cache.best(std::vector<int64_t>{}, {}, true).tokens == 0, "empty prompt cannot match");
cache.put(image({1, 2, 3}));
check(cache.best(a, {}, true).index == 1, "ties prefer most recently parked");
}
{
auto s = image({1, 2, 3});
s.live.imgs = {{1, 123}};
ConversationCache cache(1024, 3);
cache.put(std::move(s));
check(cache.best(a, {{1,123}}, true).tokens == 3, "same image can resume");
check(cache.best(a, {{1,124}}, true).tokens == 0, "different image pixels invalidate same pad tokens");
check(cache.best(a, {}, true).tokens == 0, "missing image invalidates prefix");
check(cache.best(a, {{1,123},{2,45}}, true).tokens == 0, "additional image in cached prefix invalidates");
check(cache.best(a, {{1,123},{3,45}}, true).tokens == 3, "image after cached prefix does not invalidate");
}
{
const size_t one = image({1,2,3}).bytes();
ConversationCache cache(one*2, 8);
cache.put(image({1,2,3})); cache.put(image({9,8,7}));
check(cache.bytes() == one*2, "budget holds two exact-sized images");
auto held = cache.take(cache.best(a, {}, true).index);
check(cache.make_room(one, held.bytes()), "count in-flight image when reserving outgoing snapshot");
check(cache.size() == 0, "in-flight reservation evicts otherwise fitting B");
check(cache.put(image({5,6,7}), held.bytes()), "insert with in-flight accounting");
check(cache.bytes()+held.bytes() <= one*2, "exchange obeys byte budget");
check(!cache.make_room(one+1, one), "oversized exchange rejected");
check(cache.size() == 1, "oversized snapshot does not evict useful entries");
auto huge = image({1}); huge.live.gdn.resize(one*3);
check(!cache.put(std::move(huge)), "oversized image rejected");
check(cache.size() == 1, "oversized put leaves cache unchanged");
check(!cache.make_room(0, one*2+1), "held larger than budget cannot underflow");
}
{
// #342: MAIN -> SUB (9 turns) -> MAIN with 4 slots. Every SUB turn parks the previous turn's live state
// (its reply as generated, which the next request re-rendered) with a chain that holds that turn's
// boundary checkpoint; the copy a turn back adds only that stale tail and is dropped, so MAIN survives.
auto cp = [](std::vector<int32_t> ids) { ConversationCheckpoint c; c.ids = std::move(ids); return c; };
auto with = [](std::vector<int32_t> prefix, std::initializer_list<int32_t> more) {
prefix.insert(prefix.end(), more); return prefix;
};
const std::vector<int32_t> root = {1, 2, 3, 4};
SavedConversation main = image({});
main.live.ids = with(with(root, {10, 11, 12}), {13, 14});
main.checkpoints = {cp(root), cp(with(root, {10, 11, 12}))};
const size_t big = 1 << 20;
ConversationCache cache(big, 4);
check(cache.put(std::move(main)), "park MAIN");
std::vector<int32_t> history = with(root, {20}); // SUB's conversation so far, re-rendered
std::vector<ConversationCheckpoint> chain = {cp(root)};
for (int turn = 1; turn <= 9; ++turn) {
chain.push_back(cp(history)); // the turn boundary the next request resumes from
SavedConversation sub = image({});
sub.live.ids = with(history, {900, (int32_t) turn}); // + the reply with its thinking (stale tail)
sub.checkpoints = chain;
check(cache.put(std::move(sub)), "park SUB turn");
check(cache.size() <= 2, "one parked copy of SUB at a time");
history = with(history, {30, (int32_t) turn}); // the reply as the next request renders it
}
check(cache.evictions() == 0 && cache.superseded() == 8, "8 superseded copies dropped, nothing evicted");
const auto m = cache.best(with(with(root, {10, 11, 12}), {13, 14, 15}), {}, true);
check(m.tokens == 9 && m.live, "MAIN still restores in full");
const auto s = cache.best(with(history, {40}), {}, true);
check(s.tokens == (int64_t) history.size() - 2, "SUB resumes from its last turn boundary");
}
{
// what is NOT superseded: another conversation sharing only the root, an entry without checkpoints, the
// other steering mode, a branch whose deepest checkpoint the new chain does not hold
auto cp = [](std::vector<int32_t> ids) { ConversationCheckpoint c; c.ids = std::move(ids); return c; };
ConversationCache cache(1 << 20, 8);
SavedConversation other = image({1, 2, 3, 4, 50, 51, 52});
other.checkpoints = {cp({1, 2, 3, 4}), cp({1, 2, 3, 4, 50, 51})};
cache.put(std::move(other));
cache.put(image({1, 2, 3, 4, 7, 7})); // no checkpoints
SavedConversation steered = image({1, 2, 3, 4, 60, 61}, false);
steered.checkpoints = {cp({1, 2, 3, 4, 60})};
cache.put(std::move(steered));
SavedConversation branch = image({1, 2, 3, 4, 60, 70, 71});
branch.checkpoints = {cp({1, 2, 3, 4}), cp({1, 2, 3, 4, 60, 70})};
cache.put(std::move(branch));
SavedConversation incoming = image({1, 2, 3, 4, 60, 80, 81});
incoming.checkpoints = {cp({1, 2, 3, 4}), cp({1, 2, 3, 4, 60})};
check(cache.put(std::move(incoming)), "park a conversation sharing roots with all of them");
check(cache.size() == 5 && cache.superseded() == 0, "none of them is superseded");
SavedConversation same_state = image({1, 2, 3, 4, 60, 70}); // live equal to the branch's deepest point
same_state.checkpoints = {cp({1, 2, 3, 4})};
check(cache.put(std::move(same_state)) && cache.superseded() == 1 && cache.size() == 5,
"the live state equal to an entry's deepest checkpoint supersedes it");
SavedConversation huge = image({1, 2, 3, 4, 60, 80});
huge.checkpoints = {cp({1, 2, 3, 4}), cp({1, 2, 3, 4, 60})};
huge.live.gdn.resize(2 << 20);
check(!cache.put(std::move(huge)) && cache.size() == 5 && cache.superseded() == 1,
"an oversized put drops nothing");
}
{
ConversationCache disabled(0,4), no_slots(1024,0);
check(!disabled.enabled() && !no_slots.enabled(), "both disable switches");
check(!disabled.put(image({1,2,3})) && !no_slots.put(image({1,2,3})), "disabled cache stores nothing");
check(disabled.best(a,{},true).tokens == 0, "disabled cache has no matches");
}
std::printf("conversation_cache_test: %d checks passed\n", checks);
}
|