Skip to content

Commit 436787f

Browse files
authored
llama : fix time complexity of string replacement (#9163)
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 93bc383 commit 436787f

File tree

3 files changed

+32
-14
lines changed

3 files changed

+32
-14
lines changed

common/common.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,13 +1861,19 @@ std::string string_get_sortable_timestamp() {
18611861

18621862
void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
18631863
if (search.empty()) {
1864-
return; // Avoid infinite loop if 'search' is an empty string
1864+
return;
18651865
}
1866+
std::string builder;
1867+
builder.reserve(s.length());
18661868
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-
}
1869+
size_t last_pos = 0;
1870+
while ((pos = s.find(search, last_pos)) != std::string::npos) {
1871+
builder.append(s, last_pos, pos - last_pos);
1872+
builder.append(replace);
1873+
last_pos = pos + search.length();
1874+
}
1875+
builder.append(s, last_pos, std::string::npos);
1876+
s = std::move(builder);
18711877
}
18721878

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

examples/llava/clip.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,19 @@ static std::string gguf_data_to_str(enum gguf_type type, const void * data, int
216216

217217
static void replace_all(std::string & s, const std::string & search, const std::string & replace) {
218218
if (search.empty()) {
219-
return; // Avoid infinite loop if 'search' is an empty string
219+
return;
220220
}
221+
std::string builder;
222+
builder.reserve(s.length());
221223
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-
}
224+
size_t last_pos = 0;
225+
while ((pos = s.find(search, last_pos)) != std::string::npos) {
226+
builder.append(s, last_pos, pos - last_pos);
227+
builder.append(replace);
228+
last_pos = pos + search.length();
229+
}
230+
builder.append(s, last_pos, std::string::npos);
231+
s = std::move(builder);
226232
}
227233

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

src/llama-impl.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ void llama_log_callback_default(ggml_log_level level, const char * text, void *
3131

3232
static void replace_all(std::string & s, const std::string & search, const std::string & replace) {
3333
if (search.empty()) {
34-
return; // Avoid infinite loop if 'search' is an empty string
34+
return;
3535
}
36+
std::string builder;
37+
builder.reserve(s.length());
3638
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();
39+
size_t last_pos = 0;
40+
while ((pos = s.find(search, last_pos)) != std::string::npos) {
41+
builder.append(s, last_pos, pos - last_pos);
42+
builder.append(replace);
43+
last_pos = pos + search.length();
4044
}
45+
builder.append(s, last_pos, std::string::npos);
46+
s = std::move(builder);
4147
}

0 commit comments

Comments
 (0)