elelem-llama 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/elelem/llama/elelem.cpp +51 -5
- data/lib/elelem/llama/provider.rb +14 -3
- data/lib/elelem/llama/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0099260d12a1606376955d0d2df33c00795e344b1634ddb7b5b36ded6cdeb2f9'
|
|
4
|
+
data.tar.gz: 4b0179b842631b931caade9e87846759543b9513fdf314ec958a0663f4cfcf79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58f68b1012d8870dc6ffdeb8171ccd033c83e8ca1a4ac97ce3d9720b2c55223be3a02a2b395a0d13d14f60518377efe9cb33a95ba31b50ae0200868219df91b9
|
|
7
|
+
data.tar.gz: 89331d664c308f1c9b6ebce438953c9a613d4d54d1e13e2489ad20d09af0ffbef332d89d418e5cd82891574f0c7d848ff50cd7e0c9253fa6b829af31b7007099
|
data/ext/elelem/llama/elelem.cpp
CHANGED
|
@@ -23,9 +23,29 @@ extern "C" {
|
|
|
23
23
|
float temp;
|
|
24
24
|
uint32_t seed;
|
|
25
25
|
bool dead = false;
|
|
26
|
+
bool in_generate = false;
|
|
26
27
|
std::vector<llama_token> cached;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
typedef void (*el_token_cb)(void *userdata, const char *piece);
|
|
31
|
+
|
|
32
|
+
// Returns the length of the prefix of `s` that ends on a complete UTF-8
|
|
33
|
+
// sequence, so callers don't flush a truncated multi-byte codepoint.
|
|
34
|
+
static size_t el_utf8_safe_len(const std::string &s) {
|
|
35
|
+
size_t n = s.size();
|
|
36
|
+
size_t back = 0;
|
|
37
|
+
while (back < n && back < 4 && ((unsigned char) s[n - 1 - back] & 0xC0) == 0x80) back++;
|
|
38
|
+
if (back == n) return 0;
|
|
39
|
+
unsigned char lead = (unsigned char) s[n - 1 - back];
|
|
40
|
+
size_t seq_len = 1;
|
|
41
|
+
if ((lead & 0xE0) == 0xC0) seq_len = 2;
|
|
42
|
+
else if ((lead & 0xF0) == 0xE0) seq_len = 3;
|
|
43
|
+
else if ((lead & 0xF8) == 0xF0) seq_len = 4;
|
|
44
|
+
else if (lead >= 0x80) seq_len = back + 1; // stray continuation/invalid lead; hold it
|
|
45
|
+
if (back + 1 < seq_len) return n - back - 1;
|
|
46
|
+
return n;
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
static bool g_backend = false;
|
|
30
50
|
|
|
31
51
|
static enum ggml_log_level el_log_threshold() {
|
|
@@ -154,7 +174,7 @@ extern "C" {
|
|
|
154
174
|
return buf.c_str();
|
|
155
175
|
}
|
|
156
176
|
|
|
157
|
-
static const char *el_generate_impl(void *handle, const char *messages_json, const char *tools_json, int max_tokens) {
|
|
177
|
+
static const char *el_generate_impl(void *handle, const char *messages_json, const char *tools_json, int max_tokens, el_token_cb on_token, void *userdata) {
|
|
158
178
|
auto *h = (el_handle *) handle;
|
|
159
179
|
const llama_vocab *vocab = llama_model_get_vocab(h->model);
|
|
160
180
|
static thread_local std::string buf;
|
|
@@ -239,22 +259,40 @@ extern "C" {
|
|
|
239
259
|
int budget = max_tokens > 0 ? std::min(max_tokens, headroom) : std::max(1, headroom / 2);
|
|
240
260
|
|
|
241
261
|
std::string output;
|
|
262
|
+
std::string pending;
|
|
242
263
|
char piece[512];
|
|
243
264
|
int n_decoded = 0;
|
|
244
265
|
bool eog = false;
|
|
245
266
|
bool decode_failed = false;
|
|
267
|
+
auto last_flush = std::chrono::steady_clock::now();
|
|
246
268
|
for (int t = 0; t < budget; t++) {
|
|
247
269
|
llama_token id = llama_sampler_sample(smpl.get(), ctx, -1);
|
|
248
270
|
if (llama_vocab_is_eog(vocab, id)) { eog = true; break; }
|
|
249
271
|
int np = llama_token_to_piece(vocab, id, piece, (int32_t) sizeof(piece), 0, true);
|
|
250
|
-
if (np > 0)
|
|
272
|
+
if (np > 0) {
|
|
273
|
+
output.append(piece, np);
|
|
274
|
+
if (on_token) pending.append(piece, np);
|
|
275
|
+
}
|
|
251
276
|
n_decoded++;
|
|
277
|
+
if (on_token && !pending.empty()) {
|
|
278
|
+
auto now = std::chrono::steady_clock::now();
|
|
279
|
+
if (std::chrono::duration<double, std::milli>(now - last_flush).count() >= 50.0) {
|
|
280
|
+
size_t safe_len = el_utf8_safe_len(pending);
|
|
281
|
+
if (safe_len > 0) {
|
|
282
|
+
std::string chunk = pending.substr(0, safe_len);
|
|
283
|
+
on_token(userdata, chunk.c_str());
|
|
284
|
+
pending.erase(0, safe_len);
|
|
285
|
+
}
|
|
286
|
+
last_flush = now;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
252
289
|
if (t + 1 < budget && llama_decode(ctx, llama_batch_get_one(&id, 1)) != 0) {
|
|
253
290
|
decode_failed = true;
|
|
254
291
|
break;
|
|
255
292
|
}
|
|
256
293
|
if (t + 1 < budget) h->cached.push_back(id);
|
|
257
294
|
}
|
|
295
|
+
if (on_token && !pending.empty()) on_token(userdata, pending.c_str());
|
|
258
296
|
auto t3 = std::chrono::steady_clock::now();
|
|
259
297
|
|
|
260
298
|
if (!decode_failed) guard.committed = true;
|
|
@@ -363,14 +401,22 @@ extern "C" {
|
|
|
363
401
|
return buf.c_str();
|
|
364
402
|
}
|
|
365
403
|
|
|
366
|
-
const char *el_generate(void *handle, const char *messages_json, const char *tools_json, int max_tokens) {
|
|
404
|
+
const char *el_generate(void *handle, const char *messages_json, const char *tools_json, int max_tokens, el_token_cb on_token, void *userdata) {
|
|
367
405
|
static thread_local std::string errbuf;
|
|
368
406
|
if (!handle) return el_error(errbuf, "null handle");
|
|
369
407
|
if (!messages_json) return el_error(errbuf, "null messages");
|
|
370
|
-
|
|
408
|
+
auto *h = (el_handle *) handle;
|
|
409
|
+
if (h->dead) return el_error(errbuf, "handle is unusable after a previous fatal error");
|
|
410
|
+
if (h->in_generate) return el_error(errbuf, "generate is not reentrant");
|
|
411
|
+
|
|
412
|
+
h->in_generate = true;
|
|
413
|
+
struct reentrancy_guard {
|
|
414
|
+
el_handle *h;
|
|
415
|
+
~reentrancy_guard() { h->in_generate = false; }
|
|
416
|
+
} guard{h};
|
|
371
417
|
|
|
372
418
|
try {
|
|
373
|
-
return el_generate_impl(handle, messages_json, tools_json, max_tokens);
|
|
419
|
+
return el_generate_impl(handle, messages_json, tools_json, max_tokens, on_token, userdata);
|
|
374
420
|
} catch (const std::exception &e) {
|
|
375
421
|
((el_handle *) handle)->dead = true;
|
|
376
422
|
return el_error(errbuf, e.what());
|
|
@@ -20,7 +20,7 @@ module Elelem
|
|
|
20
20
|
lib = Fiddle.dlopen(SHIM)
|
|
21
21
|
{
|
|
22
22
|
open: Fiddle::Function.new(lib["el_open"], [V, I, I, I, F, I], V),
|
|
23
|
-
generate: Fiddle::Function.new(lib["el_generate"], [V, V, V, I], V),
|
|
23
|
+
generate: Fiddle::Function.new(lib["el_generate"], [V, V, V, I, V, V], V, need_gvl: true),
|
|
24
24
|
close: Fiddle::Function.new(lib["el_close"], [V], Fiddle::TYPE_VOID)
|
|
25
25
|
}
|
|
26
26
|
end
|
|
@@ -35,7 +35,18 @@ module Elelem
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def fetch(messages, tools = [], &block)
|
|
38
|
-
|
|
38
|
+
streamed = false
|
|
39
|
+
on_token = Fiddle::Closure::BlockCaller.new(Fiddle::TYPE_VOID, [V, V]) do |_userdata, piece|
|
|
40
|
+
streamed = true
|
|
41
|
+
text = Fiddle::Pointer.new(piece).to_s.force_encoding(Encoding::UTF_8).scrub
|
|
42
|
+
block&.call(type: "thinking", text: text)
|
|
43
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
44
|
+
Elelem.logger.warn("llama: streaming callback failed: #{e.message}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
ptr = self.class.functions[:generate].call(
|
|
48
|
+
@handle, JSON.generate(messages), JSON.generate(tools), @max_tokens, on_token, nil
|
|
49
|
+
)
|
|
39
50
|
result = JSON.parse(Fiddle::Pointer.new(ptr).to_s)
|
|
40
51
|
|
|
41
52
|
Elelem.logger.debug("llama: tool-call fallback used") if result["fallback"]
|
|
@@ -53,7 +64,7 @@ module Elelem
|
|
|
53
64
|
end
|
|
54
65
|
reasoning = result["reasoning"].to_s
|
|
55
66
|
Elelem.logger.debug("llama: reasoning: #{reasoning}") unless reasoning.empty?
|
|
56
|
-
block&.call(type: "thinking", text: reasoning) unless reasoning.empty?
|
|
67
|
+
block&.call(type: "thinking", text: reasoning) unless reasoning.empty? || streamed
|
|
57
68
|
|
|
58
69
|
content = result["content"].to_s
|
|
59
70
|
if result["error"] && content.empty?
|
data/lib/elelem/llama/version.rb
CHANGED