Skip to content

Commit 25c8d8a

Browse files
committed
Preserve suffix errors outside optional prefix selection
1 parent 32eba9a commit 25c8d8a

4 files changed

Lines changed: 246 additions & 48 deletions

File tree

‎extension/llm/batching/prefix_cache.h‎

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,8 @@ class ET_EXPERIMENTAL PrefixCache {
348348
return lookup_identity(copy_tokens(request));
349349
}
350350

351-
// Skip candidates that cannot improve on an already available prefix.
352-
std::optional<PrefixMatch> lookup_identity(
353-
const PrefixIdentity& request,
354-
std::size_t longer_than = 0) {
355-
auto match = find_identity(request, longer_than);
351+
std::optional<PrefixMatch> lookup_identity(const PrefixIdentity& request) {
352+
auto match = find_identity(request);
356353
if (match) {
357354
touch(*match);
358355
}
@@ -361,6 +358,11 @@ class ET_EXPERIMENTAL PrefixCache {
361358

362359
// Validate candidates before cloning, without refreshing recency. Call
363360
// touch() after adopting the match. The selector must not mutate this cache.
361+
// Candidates arrive in descending length order, with repeated lengths
362+
// allowed. Use immediately attempts a clone; failure advances to the next
363+
// candidate. A successful clone returns before another selection. Stop and a
364+
// miss both return nullopt; callers must retain any separate reason for
365+
// stopping.
364366
std::optional<PrefixMatch> find_identity(
365367
const PrefixIdentity& request,
366368
std::size_t longer_than = 0,
@@ -404,22 +406,14 @@ class ET_EXPERIMENTAL PrefixCache {
404406
continue;
405407
}
406408
}
407-
#if ET_HAS_EXCEPTIONS
408-
try {
409-
#endif
410-
auto session =
411-
candidate.entry->snapshot
412-
.clone_async(static_cast<Position>(candidate.matched))
413-
.get();
414-
if (session) {
415-
return PrefixMatch{
416-
std::move(*session), candidate.matched, candidate.entry->id};
417-
}
418-
#if ET_HAS_EXCEPTIONS
419-
} catch (...) {
420-
// A refused snapshot does not rule out the remaining candidates.
409+
auto session =
410+
candidate.entry->snapshot
411+
.clone_async(static_cast<Position>(candidate.matched))
412+
.get();
413+
if (session) {
414+
return PrefixMatch{
415+
std::move(*session), candidate.matched, candidate.entry->id};
421416
}
422-
#endif
423417
}
424418
#if ET_HAS_EXCEPTIONS
425419
} catch (const std::bad_alloc&) {

‎extension/llm/batching/test/prefix_cache_test.cpp‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -898,22 +898,22 @@ TEST_F(PrefixCacheTest, EqualLengthMatchesPreferTheMostRecentlyUsedSnapshot) {
898898
EXPECT_EQ(executor.clones().back().source, first_id);
899899
}
900900

901-
TEST_F(PrefixCacheTest, LookupThresholdSkipsClonesAndDoesNotRefreshLru) {
901+
TEST_F(PrefixCacheTest, FindThresholdSkipsClonesAndDoesNotRefreshLru) {
902902
PrefixCache cache(2);
903903
ASSERT_TRUE(cache.insert({1, 2, 3}, snapshot({1, 2, 3})));
904904
ASSERT_TRUE(cache.insert({8, 9}, snapshot({8, 9})));
905905
const auto request =
906906
token_identity(std::make_shared<const Tokens>(Tokens{1, 2, 3, 4}));
907907
const auto before = executor.clones().size();
908-
EXPECT_FALSE(cache.lookup_identity(request, 3));
908+
EXPECT_FALSE(cache.find_identity(request, 3));
909909
EXPECT_FALSE(
910-
cache.lookup_identity(request, std::numeric_limits<size_t>::max()));
910+
cache.find_identity(request, std::numeric_limits<size_t>::max()));
911911
EXPECT_EQ(executor.clones().size(), before);
912912
ASSERT_TRUE(cache.insert({6, 7}, snapshot({6, 7})));
913913
EXPECT_FALSE(cache.lookup({1, 2, 3, 4}));
914914
const auto other =
915915
token_identity(std::make_shared<const Tokens>(Tokens{8, 9, 10}));
916-
auto match = cache.lookup_identity(other, 1);
916+
auto match = cache.find_identity(other, 1);
917917
ASSERT_TRUE(match);
918918
EXPECT_EQ(match->matched_tokens, 2u);
919919
expect_cold_match(*match, {8, 9, 10});

‎extension/llm/serving/serving_runtime.cpp‎

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ struct ServingRuntime::Impl {
783783
}
784784
std::optional<batching::Session> cloned;
785785
std::optional<batching::PrefixMatch> match;
786+
auto fallback_limit = upto ? upto : std::numeric_limits<std::size_t>::max();
786787
if (upto) {
787788
auto selection = lookup_prefix(request, match, upto);
788789
const auto* outcome = std::get_if<Selection>(&selection);
@@ -811,17 +812,15 @@ struct ServingRuntime::Impl {
811812
// Try a retained snapshot if the live source cannot be cloned.
812813
}
813814
#endif
815+
} else {
816+
fallback_limit = upto - 1;
814817
}
815818
}
816819
if (!cloned) {
817820
if (request->cancelled.load()) {
818821
return Selection::Cancelled;
819822
}
820-
auto selection = lookup_prefix(
821-
request,
822-
match,
823-
0,
824-
upto ? upto : std::numeric_limits<std::size_t>::max());
823+
auto selection = lookup_prefix(request, match, 0, fallback_limit);
825824
if (auto* outcome = std::get_if<Selection>(&selection);
826825
!outcome || *outcome != Selection::Selected) {
827826
return selection;
@@ -1023,36 +1022,48 @@ struct ServingRuntime::Impl {
10231022
}
10241023
auto original_delta = std::move(request->request.delta);
10251024
SelectionResult result = Selection::Unavailable;
1025+
std::optional<std::size_t> previous_start;
1026+
SelectionResult selection = Selection::Unavailable;
10261027
// A lookup clone uses the new request's reserved working-session slot.
10271028
#if ET_HAS_EXCEPTIONS
10281029
try {
10291030
#endif
10301031
match = prefix_cache_.find_identity(
10311032
*request->text->prompt, longer_than, at_most, [&](std::size_t start) {
10321033
using Decision = batching::PrefixCache::MatchDecision;
1034+
if (request->cancelled.load()) {
1035+
result = Selection::Cancelled;
1036+
return Decision::Stop;
1037+
}
1038+
if (previous_start != start) {
10331039
#if ET_HAS_EXCEPTIONS
1034-
try {
1040+
try {
10351041
#endif
1036-
auto selection = select_input(request, start);
1037-
if (const auto* outcome = std::get_if<Selection>(&selection)) {
1038-
if (*outcome == Selection::Selected) {
1039-
return Decision::Use;
1040-
}
1041-
if (*outcome == Selection::Unavailable) {
1042-
return Decision::Skip;
1043-
}
1044-
} else if (
1045-
std::get<ServingError>(selection).code !=
1046-
ErrorCode::NotReady) {
1042+
selection = select_input(request, start);
1043+
#if ET_HAS_EXCEPTIONS
1044+
} catch (...) {
1045+
selection = ServingError{
1046+
ErrorCode::Internal, "prepared suffix selection failed"};
1047+
}
1048+
#endif
1049+
previous_start = start;
1050+
}
1051+
if (const auto* outcome = std::get_if<Selection>(&selection)) {
1052+
if (*outcome == Selection::Selected) {
1053+
return Decision::Use;
1054+
}
1055+
if (*outcome == Selection::Unavailable) {
10471056
return Decision::Skip;
10481057
}
1049-
result = std::move(selection);
1050-
return Decision::Stop;
1051-
#if ET_HAS_EXCEPTIONS
1052-
} catch (...) {
1058+
} else if (
1059+
longer_than &&
1060+
std::get<ServingError>(selection).code != ErrorCode::NotReady) {
1061+
// Only the preferred lookup may ignore a new boundary's error;
1062+
// ordinary lookup retains the existing input-validation contract.
10531063
return Decision::Skip;
10541064
}
1055-
#endif
1065+
result = selection;
1066+
return Decision::Stop;
10561067
});
10571068
#if ET_HAS_EXCEPTIONS
10581069
} catch (...) {

0 commit comments

Comments
 (0)