Skip to content

Commit b952977

Browse files
committed
feat: monitor repeated model output
1 parent 9bdb539 commit b952977

16 files changed

Lines changed: 929 additions & 41 deletions

rtp_llm/config/py_config_modules.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,13 @@ def to_string(self):
403403

404404
class RepetitionDetectionConfig:
405405
def __init__(self):
406+
self.output_repetition_monitor: bool = True
407+
self.output_repetition_min_repeats: int = 3
408+
self.output_repetition_min_dup_tokens: int = 32
409+
self.output_repetition_max_period: int = 512
410+
self.noncontig_repeat_min_span_tokens: int = 32
411+
self.noncontig_repeat_min_occurrences: int = 3
412+
self.noncontig_repeat_max_span_tokens: int = 256
406413
self.tool_call_loop_monitor: bool = True
407414
self.tool_call_loop_threshold: int = 5
408415
self.tool_call_loop_max_span_tokens: int = 16384
@@ -411,6 +418,13 @@ def __init__(self):
411418

412419
def to_string(self):
413420
return (
421+
f"output_repetition_monitor: {self.output_repetition_monitor}\n"
422+
f"output_repetition_min_repeats: {self.output_repetition_min_repeats}\n"
423+
f"output_repetition_min_dup_tokens: {self.output_repetition_min_dup_tokens}\n"
424+
f"output_repetition_max_period: {self.output_repetition_max_period}\n"
425+
f"noncontig_repeat_min_span_tokens: {self.noncontig_repeat_min_span_tokens}\n"
426+
f"noncontig_repeat_min_occurrences: {self.noncontig_repeat_min_occurrences}\n"
427+
f"noncontig_repeat_max_span_tokens: {self.noncontig_repeat_max_span_tokens}\n"
414428
f"tool_call_loop_monitor: {self.tool_call_loop_monitor}\n"
415429
f"tool_call_loop_threshold: {self.tool_call_loop_threshold}\n"
416430
f"tool_call_loop_max_span_tokens: {self.tool_call_loop_max_span_tokens}\n"

rtp_llm/cpp/repetition/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ load("@arch_config//:arch_select.bzl", "torch_deps")
33

44
package(default_visibility = ["//visibility:public"])
55

6+
cc_library(
7+
name = "online_repetition_tracker_core",
8+
srcs = ["OnlineRepetitionTracker.cc"],
9+
hdrs = ["OnlineRepetitionTracker.h"],
10+
copts = copts(),
11+
)
12+
613
cc_library(
714
name = "token_tool_call_loop_guard_core",
815
srcs = ["TokenToolCallLoopGuard.cc"],
@@ -15,6 +22,7 @@ cc_library(
1522
srcs = ["OnlineRepetitionPybind.cc"],
1623
copts = copts(),
1724
deps = [
25+
":online_repetition_tracker_core",
1826
":token_tool_call_loop_guard_core",
1927
] + torch_deps(),
2028
alwayslink = True,

rtp_llm/cpp/repetition/OnlineRepetitionPybind.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "rtp_llm/cpp/repetition/OnlineRepetitionTracker.h"
12
#include "rtp_llm/cpp/repetition/TokenToolCallLoopGuard.h"
23

34
#include <pybind11/pybind11.h>
@@ -8,6 +9,40 @@ namespace py = pybind11;
89
PYBIND11_MODULE(libonline_repetition_tracker, m) {
910
using namespace rtp_llm;
1011

12+
py::class_<OnlineRepetitionConfig>(m, "OnlineRepetitionConfig")
13+
.def(py::init<>())
14+
.def_readwrite("min_repeats", &OnlineRepetitionConfig::min_repeats)
15+
.def_readwrite("min_duplicate_tokens", &OnlineRepetitionConfig::min_duplicate_tokens)
16+
.def_readwrite("max_period", &OnlineRepetitionConfig::max_period)
17+
.def_readwrite("non_contiguous_min_span", &OnlineRepetitionConfig::non_contiguous_min_span)
18+
.def_readwrite("non_contiguous_min_occurrences", &OnlineRepetitionConfig::non_contiguous_min_occurrences)
19+
.def_readwrite("non_contiguous_max_span", &OnlineRepetitionConfig::non_contiguous_max_span);
20+
21+
py::class_<OnlineRepetitionResult>(m, "OnlineRepetitionResult")
22+
.def_readonly("hit", &OnlineRepetitionResult::hit)
23+
.def_readonly("repeat_unit_size", &OnlineRepetitionResult::repeat_unit_size)
24+
.def_readonly("repeat_count", &OnlineRepetitionResult::repeat_count)
25+
.def_readonly("partial_tail_tokens", &OnlineRepetitionResult::partial_tail_tokens)
26+
.def_readonly("covered_token_count", &OnlineRepetitionResult::covered_token_count)
27+
.def_readonly("duplicate_token_count", &OnlineRepetitionResult::duplicate_token_count)
28+
.def_readonly("start_index", &OnlineRepetitionResult::start_index)
29+
.def_readonly("end_index", &OnlineRepetitionResult::end_index)
30+
.def_readonly("first_detect_index", &OnlineRepetitionResult::first_detect_index)
31+
.def_readonly("non_contiguous", &OnlineRepetitionResult::non_contiguous)
32+
.def_readonly("occurrence_count", &OnlineRepetitionResult::occurrence_count);
33+
34+
py::class_<OnlineRepetitionTracker>(m, "OnlineRepetitionTracker")
35+
.def(py::init<OnlineRepetitionConfig>())
36+
.def("reset", &OnlineRepetitionTracker::reset)
37+
.def("update_many",
38+
[](OnlineRepetitionTracker& tracker, const std::vector<int>& token_ids) {
39+
py::gil_scoped_release release;
40+
return tracker.updateMany(token_ids);
41+
})
42+
.def("finalize", &OnlineRepetitionTracker::considerFinalTail)
43+
.def_property_readonly("result", [](const OnlineRepetitionTracker& tracker) { return tracker.result(); })
44+
.def_property_readonly("token_count", &OnlineRepetitionTracker::tokenCount);
45+
1146
m.def(
1247
"check_tool_call_loop",
1348
[](const std::vector<int>& input_ids,
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#include "rtp_llm/cpp/repetition/OnlineRepetitionTracker.h"
2+
3+
#include <algorithm>
4+
#include <tuple>
5+
6+
namespace rtp_llm {
7+
8+
namespace {
9+
10+
OnlineRepetitionConfig normalizeConfig(OnlineRepetitionConfig config) {
11+
config.min_repeats = std::max(3, config.min_repeats);
12+
config.min_duplicate_tokens = std::max(0, config.min_duplicate_tokens);
13+
config.max_period = std::max(1, config.max_period);
14+
config.non_contiguous_min_span = std::max(8, config.non_contiguous_min_span);
15+
config.non_contiguous_min_occurrences = std::max(2, config.non_contiguous_min_occurrences);
16+
config.non_contiguous_max_span = std::max(config.non_contiguous_min_span, config.non_contiguous_max_span);
17+
return config;
18+
}
19+
20+
bool betterResult(const OnlineRepetitionResult& lhs, const OnlineRepetitionResult& rhs) {
21+
if (!rhs.hit) {
22+
return lhs.hit;
23+
}
24+
if (!lhs.hit) {
25+
return false;
26+
}
27+
return std::make_tuple(lhs.duplicate_token_count, lhs.covered_token_count, -lhs.repeat_unit_size) >
28+
std::make_tuple(rhs.duplicate_token_count, rhs.covered_token_count, -rhs.repeat_unit_size);
29+
}
30+
31+
OnlineRepetitionResult normalizeResultForEnd(const OnlineRepetitionResult& result, int token_count) {
32+
if (!result.hit) {
33+
return result;
34+
}
35+
if (result.non_contiguous) {
36+
return result;
37+
}
38+
OnlineRepetitionResult normalized = result;
39+
const bool reaches_final_end = token_count >= 0 && result.end_index == token_count;
40+
if (reaches_final_end) {
41+
normalized.duplicate_token_count =
42+
normalized.covered_token_count - normalized.repeat_unit_size;
43+
return normalized;
44+
}
45+
46+
normalized.partial_tail_tokens = 0;
47+
normalized.covered_token_count =
48+
normalized.repeat_count * normalized.repeat_unit_size;
49+
normalized.duplicate_token_count =
50+
normalized.covered_token_count - normalized.repeat_unit_size;
51+
normalized.end_index = normalized.start_index + normalized.covered_token_count;
52+
return normalized;
53+
}
54+
55+
} // namespace
56+
57+
OnlineRepetitionTracker::OnlineRepetitionTracker(OnlineRepetitionConfig config):
58+
config_(normalizeConfig(config)),
59+
match_len_by_period_(static_cast<std::size_t>(config_.max_period) + 1, 0),
60+
last_match_index_by_period_(static_cast<std::size_t>(config_.max_period) + 1, -2) {}
61+
62+
void OnlineRepetitionTracker::reset() {
63+
token_count_ = 0;
64+
result_ = OnlineRepetitionResult();
65+
positions_by_token_.clear();
66+
tokens_.clear();
67+
prefix_hash_.assign(1, 0);
68+
hash_power_.assign(1, 1);
69+
span_occurrences_.clear();
70+
std::fill(match_len_by_period_.begin(), match_len_by_period_.end(), 0);
71+
std::fill(last_match_index_by_period_.begin(), last_match_index_by_period_.end(), -2);
72+
}
73+
74+
std::uint64_t OnlineRepetitionTracker::spanHash(int start, int length) const {
75+
return prefix_hash_[start + length] - prefix_hash_[start] * hash_power_[length];
76+
}
77+
78+
bool OnlineRepetitionTracker::considerNonContiguousSpans(int token_index) {
79+
static constexpr std::uint64_t kHashBase = 0x9e3779b185ebca87ULL;
80+
bool hit_now = false;
81+
for (int length = config_.non_contiguous_min_span; length <= config_.non_contiguous_max_span; length *= 2) {
82+
if (length > token_count_) {
83+
break;
84+
}
85+
const int start = token_count_ - length;
86+
const std::uint64_t hash = spanHash(start, length);
87+
const std::uint64_t key = hash ^ (static_cast<std::uint64_t>(length) * kHashBase);
88+
auto [it, inserted] = span_occurrences_.try_emplace(key, SpanOccurrence{start, start, 1});
89+
if (inserted) {
90+
continue;
91+
}
92+
auto& occurrence = it->second;
93+
if (start < occurrence.last_start + length) {
94+
continue;
95+
}
96+
if (!std::equal(tokens_.begin() + occurrence.last_start,
97+
tokens_.begin() + occurrence.last_start + length,
98+
tokens_.begin() + start)) {
99+
occurrence = SpanOccurrence{start, start, 1};
100+
continue;
101+
}
102+
occurrence.last_start = start;
103+
++occurrence.count;
104+
const int duplicate_tokens = (occurrence.count - 1) * length;
105+
if (occurrence.count < config_.non_contiguous_min_occurrences ||
106+
duplicate_tokens < config_.min_duplicate_tokens) {
107+
continue;
108+
}
109+
OnlineRepetitionResult candidate;
110+
candidate.hit = true;
111+
candidate.repeat_unit_size = length;
112+
candidate.repeat_count = occurrence.count;
113+
candidate.covered_token_count = occurrence.count * length;
114+
candidate.duplicate_token_count = duplicate_tokens;
115+
candidate.start_index = occurrence.first_start;
116+
candidate.end_index = token_index + 1;
117+
candidate.first_detect_index = token_index;
118+
candidate.non_contiguous = true;
119+
candidate.occurrence_count = occurrence.count;
120+
if (!result_.hit || betterResult(candidate, result_)) {
121+
result_ = candidate;
122+
}
123+
hit_now = true;
124+
}
125+
return hit_now;
126+
}
127+
128+
bool OnlineRepetitionTracker::considerCandidate(int period, int covered, int token_index, bool include_partial_tail) {
129+
const int repeat_count = covered / period;
130+
if (repeat_count < config_.min_repeats) {
131+
return false;
132+
}
133+
134+
const int complete_covered = repeat_count * period;
135+
const int duplicate_tokens = (include_partial_tail ? covered : complete_covered) - period;
136+
if (duplicate_tokens < config_.min_duplicate_tokens) {
137+
return false;
138+
}
139+
140+
OnlineRepetitionResult candidate;
141+
candidate.hit = true;
142+
candidate.repeat_unit_size = period;
143+
candidate.repeat_count = repeat_count;
144+
candidate.partial_tail_tokens = covered % period;
145+
candidate.covered_token_count = covered;
146+
candidate.duplicate_token_count = duplicate_tokens;
147+
candidate.start_index = token_index - covered + 1;
148+
candidate.end_index = token_index + 1;
149+
candidate.first_detect_index = token_index;
150+
151+
if (!result_.hit || betterResult(candidate, result_)) {
152+
result_ = candidate;
153+
}
154+
return true;
155+
}
156+
157+
bool OnlineRepetitionTracker::considerMatch(int period, int match_len, int token_index) {
158+
return considerCandidate(period, match_len + period, token_index, false);
159+
}
160+
161+
bool OnlineRepetitionTracker::update(int token_id) {
162+
const int token_index = token_count_++;
163+
static constexpr std::uint64_t kHashBase = 0x9e3779b185ebca87ULL;
164+
tokens_.push_back(token_id);
165+
prefix_hash_.push_back(prefix_hash_.back() * kHashBase + static_cast<std::uint32_t>(token_id) + 1);
166+
hash_power_.push_back(hash_power_.back() * kHashBase);
167+
auto& positions = positions_by_token_[token_id];
168+
const int oldest_kept = token_index - config_.max_period;
169+
while (positions.first < positions.values.size() &&
170+
positions.values[positions.first] < oldest_kept) {
171+
++positions.first;
172+
}
173+
174+
bool hit_now = false;
175+
for (std::size_t pos_index = positions.values.size(); pos_index > positions.first;) {
176+
--pos_index;
177+
const int previous_index = positions.values[pos_index];
178+
const int period = token_index - previous_index;
179+
if (period <= 0 || period > config_.max_period) {
180+
continue;
181+
}
182+
183+
int match_len = 1;
184+
if (last_match_index_by_period_[period] == token_index - 1) {
185+
match_len = match_len_by_period_[period] + 1;
186+
}
187+
match_len_by_period_[period] = match_len;
188+
last_match_index_by_period_[period] = token_index;
189+
190+
hit_now = considerMatch(period, match_len, token_index) || hit_now;
191+
}
192+
193+
positions.values.push_back(token_index);
194+
hit_now = considerNonContiguousSpans(token_index) || hit_now;
195+
return result_.hit || hit_now;
196+
}
197+
198+
bool OnlineRepetitionTracker::considerFinalTail() {
199+
if (token_count_ <= 0) {
200+
return result_.hit;
201+
}
202+
const int token_index = token_count_ - 1;
203+
bool hit_now = false;
204+
const int max_period = std::min(config_.max_period, token_count_ - 1);
205+
for (int period = 1; period <= max_period; ++period) {
206+
if (last_match_index_by_period_[period] != token_index) {
207+
continue;
208+
}
209+
const int covered = std::min(match_len_by_period_[period] + period, token_count_);
210+
hit_now = considerCandidate(period, covered, token_index, true) || hit_now;
211+
}
212+
return result_.hit || hit_now;
213+
}
214+
215+
bool OnlineRepetitionTracker::updateMany(const std::vector<int>& token_ids) {
216+
bool hit = result_.hit;
217+
for (int token_id : token_ids) {
218+
hit = update(token_id) || hit;
219+
}
220+
return hit;
221+
}
222+
223+
OnlineRepetitionResult detectOnlineRepetitionHitOnly(
224+
const std::vector<int>& token_ids,
225+
OnlineRepetitionConfig config) {
226+
OnlineRepetitionTracker tracker(config);
227+
for (int token_id : token_ids) {
228+
if (tracker.update(token_id)) {
229+
return normalizeResultForEnd(tracker.result(), -1);
230+
}
231+
}
232+
tracker.considerFinalTail();
233+
return normalizeResultForEnd(tracker.result(), -1);
234+
}
235+
236+
OnlineRepetitionResult detectOnlineRepetitionMax(
237+
const std::vector<int>& token_ids,
238+
OnlineRepetitionConfig config) {
239+
OnlineRepetitionTracker tracker(config);
240+
tracker.updateMany(token_ids);
241+
tracker.considerFinalTail();
242+
return normalizeResultForEnd(tracker.result(), static_cast<int>(token_ids.size()));
243+
}
244+
245+
} // namespace rtp_llm

0 commit comments

Comments
 (0)