Skip to content

Commit b3c41a8

Browse files
committed
Fix time complexity of string replacement
This change fixes a bug where replacing text in a very long string could cause llama.cpp to hang indefinitely. This is because the algorithm used was quadratic, due to memmove() when s.replace() is called in a loop. It seems most search results and LLM responses actually provide the O(n**2) algorithm, which is a great tragedy. Using a builder string fixes things
1 parent e11bd85 commit b3c41a8

File tree

3 files changed

+35
-20
lines changed

3 files changed

+35
-20
lines changed

common/common.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,14 +1860,19 @@ std::string string_get_sortable_timestamp() {
18601860
}
18611861

18621862
void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
1863-
if (search.empty()) {
1864-
return; // Avoid infinite loop if 'search' is an empty string
1865-
}
1863+
if (search.empty())
1864+
return;
1865+
std::string builder;
1866+
builder.reserve(s.length());
18661867
size_t pos = 0;
1867-
while ((pos = s.find(search, pos)) != std::string::npos) {
1868-
s.replace(pos, search.length(), replace);
1869-
pos += replace.length();
1870-
}
1868+
size_t last_pos = 0;
1869+
while ((pos = s.find(search, last_pos)) != std::string::npos) {
1870+
builder.append(s, last_pos, pos - last_pos);
1871+
builder.append(replace);
1872+
last_pos = pos + search.length();
1873+
}
1874+
builder.append(s, last_pos, std::string::npos);
1875+
s = std::move(builder);
18711876
}
18721877

18731878
void string_process_escapes(std::string & input) {

examples/llava/clip.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,19 @@ static std::string gguf_data_to_str(enum gguf_type type, const void * data, int
215215
}
216216

217217
static void replace_all(std::string & s, const std::string & search, const std::string & replace) {
218-
if (search.empty()) {
219-
return; // Avoid infinite loop if 'search' is an empty string
220-
}
218+
if (search.empty())
219+
return;
220+
std::string builder;
221+
builder.reserve(s.length());
221222
size_t pos = 0;
222-
while ((pos = s.find(search, pos)) != std::string::npos) {
223-
s.replace(pos, search.length(), replace);
224-
pos += replace.length();
225-
}
223+
size_t last_pos = 0;
224+
while ((pos = s.find(search, last_pos)) != std::string::npos) {
225+
builder.append(s, last_pos, pos - last_pos);
226+
builder.append(replace);
227+
last_pos = pos + search.length();
228+
}
229+
builder.append(s, last_pos, std::string::npos);
230+
s = std::move(builder);
226231
}
227232

228233
static std::string gguf_kv_to_str(const struct gguf_context * ctx_gguf, int i) {

src/llama-impl.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@ void llama_log_callback_default(ggml_log_level level, const char * text, void *
3030
//
3131

3232
static void replace_all(std::string & s, const std::string & search, const std::string & replace) {
33-
if (search.empty()) {
34-
return; // Avoid infinite loop if 'search' is an empty string
35-
}
33+
if (search.empty())
34+
return;
35+
std::string builder;
36+
builder.reserve(s.length());
3637
size_t pos = 0;
37-
while ((pos = s.find(search, pos)) != std::string::npos) {
38-
s.replace(pos, search.length(), replace);
39-
pos += replace.length();
38+
size_t last_pos = 0;
39+
while ((pos = s.find(search, last_pos)) != std::string::npos) {
40+
builder.append(s, last_pos, pos - last_pos);
41+
builder.append(replace);
42+
last_pos = pos + search.length();
4043
}
44+
builder.append(s, last_pos, std::string::npos);
45+
s = std::move(builder);
4146
}

0 commit comments

Comments
 (0)