rllama 1.1.0-aarch64-linux-gnu → 1.2.0-aarch64-linux-gnu
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/LICENSE +18 -0
- data/README.md +100 -8
- data/lib/rllama/aarch64-linux/libggml-base.so +1 -0
- data/lib/rllama/aarch64-linux/libggml-base.so.0 +1 -0
- data/lib/rllama/aarch64-linux/libggml-base.so.0.17.0 +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv8.0_1.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv8.2_1.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv8.2_2.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv8.2_3.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv8.6_1.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv8.6_2.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv9.2_1.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-armv9.2_2.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-rpc.so +0 -0
- data/lib/rllama/aarch64-linux/libggml.so +1 -0
- data/lib/rllama/aarch64-linux/libggml.so.0 +1 -0
- data/lib/rllama/aarch64-linux/libggml.so.0.17.0 +0 -0
- data/lib/rllama/aarch64-linux/libllama-common.so +1 -0
- data/lib/rllama/aarch64-linux/libllama-common.so.0 +1 -0
- data/lib/rllama/aarch64-linux/libllama-common.so.0.0.10067 +0 -0
- data/lib/rllama/aarch64-linux/libllama.so +1 -0
- data/lib/rllama/aarch64-linux/libllama.so.0 +1 -0
- data/lib/rllama/aarch64-linux/libllama.so.0.0.10067 +0 -0
- data/lib/rllama/aarch64-linux/libllama_common.so +0 -0
- data/lib/rllama/common.rb +134 -0
- data/lib/rllama/context.rb +220 -51
- data/lib/rllama/cpp.rb +8 -10
- data/lib/rllama/model.rb +56 -58
- data/lib/rllama/version.rb +1 -1
- data/lib/rllama.rb +2 -1
- data/licenses/LICENSE-boringssl +238 -0
- data/licenses/LICENSE-cpp-httplib +22 -0
- data/licenses/LICENSE-json +21 -0
- data/licenses/LICENSE-llama.cpp +21 -0
- metadata +18 -11
- data/lib/rllama/aarch64-linux/libggml-base.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-alderlake.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-haswell.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-icelake.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-sandybridge.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-sapphirerapids.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-skylakex.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-sse42.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu-x64.so +0 -0
- data/lib/rllama/aarch64-linux/libggml-cpu.so +0 -0
- data/lib/rllama/aarch64-linux/libggml.so +0 -0
- data/lib/rllama/aarch64-linux/libllama.so +0 -0
data/lib/rllama/context.rb
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'etc'
|
|
4
|
+
require 'json'
|
|
4
5
|
|
|
5
6
|
module Rllama
|
|
6
7
|
class Context
|
|
8
|
+
TOOL_CALL_ID_PREFIX = 'call_'
|
|
9
|
+
GRAMMAR_TYPE_TOOL_CALLS = 3
|
|
10
|
+
|
|
7
11
|
attr_reader :messages, :n_ctx, :n_batch, :n_past
|
|
8
12
|
|
|
9
|
-
def initialize(model, embeddings: false, n_ctx: nil, n_batch: nil, n_threads: Etc.nprocessors
|
|
13
|
+
def initialize(model, embeddings: false, n_ctx: nil, n_batch: nil, n_threads: Etc.nprocessors,
|
|
14
|
+
system: nil, tools: nil, reasoning: false)
|
|
10
15
|
@model = model
|
|
16
|
+
@tools = tools
|
|
17
|
+
@reasoning = reasoning
|
|
11
18
|
@n_ctx = n_ctx
|
|
12
19
|
@n_batch = n_batch
|
|
13
20
|
@embeddings = embeddings
|
|
@@ -38,13 +45,32 @@ module Rllama
|
|
|
38
45
|
|
|
39
46
|
raise Error, 'Failed to create the llama_context' if @pointer.null?
|
|
40
47
|
|
|
48
|
+
@n_ctx = Cpp.llama_n_ctx(@pointer)
|
|
49
|
+
@n_batch = Cpp.llama_n_batch(@pointer)
|
|
50
|
+
|
|
41
51
|
@n_past = 0
|
|
52
|
+
@cache_tokens = []
|
|
53
|
+
@tool_call_count = 0
|
|
42
54
|
@messages = []
|
|
55
|
+
@messages << { role: 'system', content: system } if system
|
|
56
|
+
|
|
57
|
+
prefill if !@embeddings && (system || tools)
|
|
43
58
|
end
|
|
44
59
|
|
|
45
|
-
def generate(message, role: 'user', max_tokens: @n_ctx, temperature:
|
|
46
|
-
seed: nil, system: nil)
|
|
47
|
-
|
|
60
|
+
def generate(message, role: 'user', max_tokens: @n_ctx, temperature: nil, top_k: nil, top_p: nil, min_p: nil,
|
|
61
|
+
seed: nil, system: nil, tools: nil, reasoning: nil, &block)
|
|
62
|
+
temperature, top_k, top_p, min_p = resolve_sampling(temperature, top_k, top_p, min_p)
|
|
63
|
+
|
|
64
|
+
@tools = tools unless tools.nil?
|
|
65
|
+
@reasoning = reasoning unless reasoning.nil?
|
|
66
|
+
|
|
67
|
+
if system
|
|
68
|
+
if @messages.dig(0, :role).to_s == 'system'
|
|
69
|
+
@messages.first[:content] = system
|
|
70
|
+
else
|
|
71
|
+
@messages.unshift(role: 'system', content: system)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
48
74
|
|
|
49
75
|
if message.is_a?(Array)
|
|
50
76
|
@messages.push(*message)
|
|
@@ -54,51 +80,19 @@ module Rllama
|
|
|
54
80
|
@messages << { role: role, content: message }
|
|
55
81
|
end
|
|
56
82
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
n_prompt_tokens = -Cpp.llama_tokenize(@model.vocab, prompt_string, prompt_string.bytesize, nil, 0, true, true)
|
|
60
|
-
|
|
61
|
-
raise Error, 'Prompt is too long.' if n_prompt_tokens.negative?
|
|
62
|
-
|
|
63
|
-
prompt_tokens_ptr = FFI::MemoryPointer.new(:int32, n_prompt_tokens)
|
|
64
|
-
tokens_written = Cpp.llama_tokenize(@model.vocab, prompt_string, prompt_string.bytesize, prompt_tokens_ptr,
|
|
65
|
-
n_prompt_tokens, true, true)
|
|
66
|
-
|
|
67
|
-
raise Error, 'Failed to tokenize prompt.' if tokens_written.negative?
|
|
68
|
-
|
|
69
|
-
new_token_count = tokens_written - @n_past
|
|
70
|
-
|
|
71
|
-
if new_token_count.positive?
|
|
72
|
-
new_tokens_ptr = prompt_tokens_ptr + (@n_past * FFI.type_size(:int32))
|
|
73
|
-
|
|
74
|
-
batch = Cpp.llama_batch_get_one(new_tokens_ptr, new_token_count)
|
|
75
|
-
|
|
76
|
-
raise Error, 'llama_decode failed.' if Cpp.llama_decode(@pointer, batch) != 0
|
|
77
|
-
|
|
78
|
-
@n_past = tokens_written
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
chain_params = Cpp.llama_sampler_chain_default_params
|
|
82
|
-
sampler_chain = Cpp.llama_sampler_chain_init(chain_params)
|
|
83
|
+
applied = @model.apply_chat_template(@messages, tools: @tools, enable_thinking: @reasoning)
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
Cpp.llama_sampler_chain_add(sampler_chain, Cpp.llama_sampler_init_top_k(top_k)) if top_k&.positive?
|
|
86
|
-
Cpp.llama_sampler_chain_add(sampler_chain, Cpp.llama_sampler_init_top_p(top_p, 1)) if top_p && top_p < 1.0
|
|
87
|
-
if temperature&.positive?
|
|
88
|
-
Cpp.llama_sampler_chain_add(sampler_chain,
|
|
89
|
-
Cpp.llama_sampler_init_temp(temperature))
|
|
90
|
-
end
|
|
85
|
+
decode_prompt(applied['prompt'])
|
|
91
86
|
|
|
92
|
-
is_probabilistic = temperature&.positive? || top_k&.positive? || (top_p && top_p < 1.0) || !min_p.nil?
|
|
93
87
|
rng_seed = seed || (Random.new_seed & 0xFFFFFFFF)
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
end
|
|
89
|
+
sampler = Common.sampler_init(@model.pointer, sampler_params(temperature, top_k, top_p, min_p, rng_seed, applied))
|
|
90
|
+
|
|
91
|
+
stops = applied['additional_stops'].to_a.map(&:b)
|
|
92
|
+
max_stop = stops.map(&:bytesize).max || 0
|
|
100
93
|
|
|
101
94
|
n_decoded = 0
|
|
95
|
+
n_yielded = 0
|
|
102
96
|
|
|
103
97
|
generated_text = ''.b
|
|
104
98
|
|
|
@@ -111,7 +105,9 @@ module Rllama
|
|
|
111
105
|
loop do
|
|
112
106
|
break if n_decoded >= max_tokens
|
|
113
107
|
|
|
114
|
-
new_token_id =
|
|
108
|
+
new_token_id = Common.sampler_sample(sampler, @pointer)
|
|
109
|
+
|
|
110
|
+
Common.sampler_accept(sampler, new_token_id)
|
|
115
111
|
|
|
116
112
|
break if Cpp.llama_vocab_is_eog(@model.vocab, new_token_id)
|
|
117
113
|
|
|
@@ -119,10 +115,10 @@ module Rllama
|
|
|
119
115
|
n_chars = Cpp.llama_token_to_piece(@model.vocab, new_token_id, buffer, buffer.size, 0, true)
|
|
120
116
|
|
|
121
117
|
if n_chars >= 0
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
stop_at, n_yielded = emit_piece(generated_text, buffer.read_string(n_chars), stops, max_stop, n_yielded,
|
|
119
|
+
&block)
|
|
120
|
+
|
|
121
|
+
break if stop_at
|
|
126
122
|
end
|
|
127
123
|
|
|
128
124
|
token_ptr = FFI::MemoryPointer.new(:int32, 1).put_int32(0, new_token_id)
|
|
@@ -132,24 +128,54 @@ module Rllama
|
|
|
132
128
|
raise Error, 'llama_decode failed.' if Cpp.llama_decode(@pointer, batch) != 0
|
|
133
129
|
|
|
134
130
|
@n_past += 1
|
|
131
|
+
@cache_tokens << new_token_id
|
|
135
132
|
n_decoded += 1
|
|
136
133
|
end
|
|
137
134
|
|
|
135
|
+
if block_given? && n_yielded < generated_text.bytesize
|
|
136
|
+
yield generated_text.byteslice(n_yielded..).force_encoding(Encoding::UTF_8)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
generated_text.force_encoding(Encoding::UTF_8)
|
|
140
|
+
|
|
138
141
|
end_time = Time.now
|
|
139
142
|
|
|
140
143
|
duration = end_time - start_time
|
|
141
144
|
|
|
142
145
|
tps = n_decoded.positive? && duration.positive? ? n_decoded / duration : 0
|
|
143
146
|
|
|
144
|
-
|
|
147
|
+
Common.sampler_free(sampler)
|
|
148
|
+
|
|
149
|
+
text = generated_text
|
|
150
|
+
reasoning = nil
|
|
151
|
+
tool_calls = []
|
|
152
|
+
|
|
153
|
+
if @tools || @reasoning
|
|
154
|
+
parsed = Common.chat_parse(generated_text, applied, reasoning: @reasoning)
|
|
155
|
+
|
|
156
|
+
reasoning = parsed['reasoning_content']
|
|
157
|
+
reasoning = nil if reasoning&.empty?
|
|
158
|
+
tool_calls = extract_tool_calls(parsed, assistant_message) if @tools
|
|
159
|
+
|
|
160
|
+
if @reasoning && tool_calls.empty?
|
|
161
|
+
text = parsed['content'].to_s
|
|
162
|
+
assistant_message[:content] = text
|
|
163
|
+
end
|
|
164
|
+
end
|
|
145
165
|
|
|
146
166
|
Result.new(
|
|
147
|
-
text
|
|
167
|
+
text:,
|
|
168
|
+
reasoning:,
|
|
169
|
+
tool_calls:,
|
|
148
170
|
stats: {
|
|
149
171
|
duration:,
|
|
150
172
|
tokens_generated: n_decoded,
|
|
151
173
|
tps:,
|
|
152
|
-
seed: rng_seed
|
|
174
|
+
seed: rng_seed,
|
|
175
|
+
temperature:,
|
|
176
|
+
top_k:,
|
|
177
|
+
top_p:,
|
|
178
|
+
min_p:
|
|
153
179
|
}
|
|
154
180
|
)
|
|
155
181
|
end
|
|
@@ -248,6 +274,149 @@ module Rllama
|
|
|
248
274
|
Cpp.llama_free(@pointer)
|
|
249
275
|
end
|
|
250
276
|
|
|
277
|
+
def prefill
|
|
278
|
+
decode_prompt(@model.build_chat_template(@messages, tools: @tools, add_generation_prompt: false,
|
|
279
|
+
enable_thinking: @reasoning))
|
|
280
|
+
rescue StandardError
|
|
281
|
+
nil
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def extract_tool_calls(parsed, assistant_message)
|
|
285
|
+
calls = (parsed['tool_calls'] || []).map do |call|
|
|
286
|
+
id = call['id'].to_s
|
|
287
|
+
id = "#{TOOL_CALL_ID_PREFIX}#{@tool_call_count += 1}" if id.empty?
|
|
288
|
+
|
|
289
|
+
arguments = begin
|
|
290
|
+
JSON.parse(call.dig('function', 'arguments').to_s)
|
|
291
|
+
rescue JSON::ParserError
|
|
292
|
+
{}
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
{ name: call.dig('function', 'name'), arguments:, id: }
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
return [] if calls.empty?
|
|
299
|
+
|
|
300
|
+
assistant_message.replace(
|
|
301
|
+
role: 'assistant',
|
|
302
|
+
content: nil,
|
|
303
|
+
tool_calls: calls.map do |call|
|
|
304
|
+
{ type: 'function', id: call[:id],
|
|
305
|
+
function: { name: call[:name], arguments: call[:arguments].to_json } }
|
|
306
|
+
end
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
calls
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def decode_prompt(prompt_string)
|
|
313
|
+
n_prompt_tokens = -Cpp.llama_tokenize(@model.vocab, prompt_string, prompt_string.bytesize, nil, 0, true, true)
|
|
314
|
+
|
|
315
|
+
raise Error, 'Prompt is too long.' if n_prompt_tokens.negative?
|
|
316
|
+
|
|
317
|
+
prompt_tokens_ptr = FFI::MemoryPointer.new(:int32, n_prompt_tokens)
|
|
318
|
+
tokens_written = Cpp.llama_tokenize(@model.vocab, prompt_string, prompt_string.bytesize, prompt_tokens_ptr,
|
|
319
|
+
n_prompt_tokens, true, true)
|
|
320
|
+
|
|
321
|
+
raise Error, 'Failed to tokenize prompt.' if tokens_written.negative?
|
|
322
|
+
|
|
323
|
+
prompt_tokens = prompt_tokens_ptr.read_array_of_int32(tokens_written)
|
|
324
|
+
|
|
325
|
+
common = common_prefix_length(prompt_tokens)
|
|
326
|
+
|
|
327
|
+
if common < @cache_tokens.length
|
|
328
|
+
memory = Cpp.llama_get_memory(@pointer)
|
|
329
|
+
|
|
330
|
+
if memory.null? || !Cpp.llama_memory_seq_rm(memory, 0, common, -1)
|
|
331
|
+
Cpp.llama_memory_clear(memory, true) unless memory.null?
|
|
332
|
+
|
|
333
|
+
common = 0
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
@n_past = common
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
while tokens_written > @n_past
|
|
340
|
+
n_eval = [tokens_written - @n_past, @n_batch].min
|
|
341
|
+
|
|
342
|
+
new_tokens_ptr = prompt_tokens_ptr + (@n_past * FFI.type_size(:int32))
|
|
343
|
+
|
|
344
|
+
batch = Cpp.llama_batch_get_one(new_tokens_ptr, n_eval)
|
|
345
|
+
|
|
346
|
+
raise Error, 'llama_decode failed.' if Cpp.llama_decode(@pointer, batch) != 0
|
|
347
|
+
|
|
348
|
+
@n_past += n_eval
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
@cache_tokens = prompt_tokens
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def common_prefix_length(tokens)
|
|
355
|
+
limit = [@cache_tokens.length, tokens.length - 1].min
|
|
356
|
+
|
|
357
|
+
common = 0
|
|
358
|
+
common += 1 while common < limit && @cache_tokens[common] == tokens[common]
|
|
359
|
+
|
|
360
|
+
common
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def sampler_params(temperature, top_k, top_p, min_p, seed, applied)
|
|
364
|
+
params = {
|
|
365
|
+
temp: temperature || 0.0,
|
|
366
|
+
top_k: top_k&.positive? ? top_k : 0,
|
|
367
|
+
top_p: top_p || 1.0,
|
|
368
|
+
min_p: min_p || 0.0,
|
|
369
|
+
seed:
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
grammar = applied['grammar'].to_s
|
|
373
|
+
|
|
374
|
+
unless grammar.empty?
|
|
375
|
+
params.merge!(grammar:, grammar_type: GRAMMAR_TYPE_TOOL_CALLS,
|
|
376
|
+
grammar_lazy: applied['grammar_lazy'],
|
|
377
|
+
grammar_triggers: applied['grammar_triggers'],
|
|
378
|
+
generation_prompt: applied['generation_prompt'])
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
params
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def emit_piece(text, piece, stops, max_stop, n_yielded, &block)
|
|
385
|
+
tail_from = [text.bytesize - max_stop + 1, 0].max
|
|
386
|
+
text << piece
|
|
387
|
+
|
|
388
|
+
stop_at = max_stop.positive? ? stops.filter_map { |stop| text.index(stop, tail_from) }.min : nil
|
|
389
|
+
text.slice!(stop_at..) if stop_at
|
|
390
|
+
|
|
391
|
+
if block
|
|
392
|
+
safe = text.bytesize - (stop_at ? 0 : stop_holdback(text, stops))
|
|
393
|
+
|
|
394
|
+
if safe > n_yielded
|
|
395
|
+
yield(text.byteslice(n_yielded, safe - n_yielded).force_encoding(Encoding::UTF_8))
|
|
396
|
+
n_yielded = safe
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
[stop_at, n_yielded]
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def stop_holdback(text, stops)
|
|
404
|
+
stops.map do |stop|
|
|
405
|
+
(stop.bytesize - 1).downto(1).find { |n| text.end_with?(stop.byteslice(0, n)) } || 0
|
|
406
|
+
end.max || 0
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def resolve_sampling(temperature, top_k, top_p, min_p)
|
|
410
|
+
defaults = @model.sampling_defaults
|
|
411
|
+
|
|
412
|
+
[
|
|
413
|
+
temperature.nil? ? defaults[:temperature] : temperature,
|
|
414
|
+
top_k.nil? ? defaults[:top_k] : top_k,
|
|
415
|
+
top_p.nil? ? defaults[:top_p] : top_p,
|
|
416
|
+
min_p.nil? ? defaults[:min_p] : min_p
|
|
417
|
+
]
|
|
418
|
+
end
|
|
419
|
+
|
|
251
420
|
def norm(vec)
|
|
252
421
|
Math.sqrt(vec.sum { |x| x**2 })
|
|
253
422
|
end
|
data/lib/rllama/cpp.rb
CHANGED
|
@@ -328,9 +328,12 @@ module Rllama
|
|
|
328
328
|
:kv_overrides, :pointer, # const LlamaModelKvOverride*
|
|
329
329
|
:vocab_only, :bool,
|
|
330
330
|
:use_mmap, :bool,
|
|
331
|
+
:use_direct_io, :bool,
|
|
331
332
|
:use_mlock, :bool,
|
|
332
333
|
:check_tensors, :bool,
|
|
333
|
-
:use_extra_bufts, :bool
|
|
334
|
+
:use_extra_bufts, :bool,
|
|
335
|
+
:no_host, :bool,
|
|
336
|
+
:no_alloc, :bool
|
|
334
337
|
end
|
|
335
338
|
|
|
336
339
|
class LlamaContextParams < FFI::Struct
|
|
@@ -338,8 +341,11 @@ module Rllama
|
|
|
338
341
|
:n_batch, :uint32,
|
|
339
342
|
:n_ubatch, :uint32,
|
|
340
343
|
:n_seq_max, :uint32,
|
|
344
|
+
:n_rs_seq, :uint32,
|
|
345
|
+
:n_outputs_max, :uint32,
|
|
341
346
|
:n_threads, :int32,
|
|
342
347
|
:n_threads_batch, :int32,
|
|
348
|
+
:ctx_type, :int, # enum llama_context_type
|
|
343
349
|
:rope_scaling_type, :int, # enum llama_rope_scaling_type
|
|
344
350
|
:pooling_type, :int, # enum llama_pooling_type
|
|
345
351
|
:attention_type, :int, # enum llama_attention_type
|
|
@@ -378,6 +384,7 @@ module Rllama
|
|
|
378
384
|
:only_copy, :bool,
|
|
379
385
|
:pure, :bool,
|
|
380
386
|
:keep_split, :bool,
|
|
387
|
+
:dry_run, :bool,
|
|
381
388
|
:imatrix, :pointer,
|
|
382
389
|
:kv_overrides, :pointer,
|
|
383
390
|
:tensor_types, :pointer,
|
|
@@ -393,11 +400,6 @@ module Rllama
|
|
|
393
400
|
layout :no_perf, :bool
|
|
394
401
|
end
|
|
395
402
|
|
|
396
|
-
class LlamaChatMessage < FFI::Struct
|
|
397
|
-
layout :role, :pointer,
|
|
398
|
-
:content, :pointer
|
|
399
|
-
end
|
|
400
|
-
|
|
401
403
|
class LlamaSamplerI < FFI::Struct; end
|
|
402
404
|
|
|
403
405
|
class LlamaSampler < FFI::Struct
|
|
@@ -623,10 +625,6 @@ module Rllama
|
|
|
623
625
|
attach_function :llama_token_to_piece, %i[llama_vocab_p llama_token pointer int32 int32 bool], :int32
|
|
624
626
|
attach_function :llama_detokenize, %i[llama_vocab_p pointer int32 pointer int32 bool bool], :int32
|
|
625
627
|
|
|
626
|
-
# Chat templates
|
|
627
|
-
attach_function :llama_chat_apply_template, %i[string pointer size_t bool pointer int32], :int32
|
|
628
|
-
attach_function :llama_chat_builtin_templates, %i[pointer size_t], :int32
|
|
629
|
-
|
|
630
628
|
# Sampling API
|
|
631
629
|
attach_function :llama_sampler_init, %i[pointer llama_sampler_context_t], :llama_sampler_p
|
|
632
630
|
attach_function :llama_sampler_name, [:llama_sampler_p], :string
|
data/lib/rllama/model.rb
CHANGED
|
@@ -4,15 +4,7 @@ module Rllama
|
|
|
4
4
|
class Model
|
|
5
5
|
DEFAULT_CONTEXT_LENGTH = 2**13
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
'gemma4' => {
|
|
9
|
-
bos: '<bos>',
|
|
10
|
-
role_map: { 'assistant' => 'model' },
|
|
11
|
-
turn_start: ->(role) { "<|turn>#{role}\n" },
|
|
12
|
-
turn_end: "<turn|>\n",
|
|
13
|
-
generation_prompt: "<|turn>model\n"
|
|
14
|
-
}
|
|
15
|
-
}.freeze
|
|
7
|
+
DEFAULT_SAMPLING = { temperature: 0.8, top_k: 40, top_p: 0.95, min_p: 0.05 }.freeze
|
|
16
8
|
|
|
17
9
|
attr_reader :pointer
|
|
18
10
|
|
|
@@ -46,21 +38,35 @@ module Rllama
|
|
|
46
38
|
@n_ctx_train ||= Cpp.llama_model_n_ctx_train(@pointer)
|
|
47
39
|
end
|
|
48
40
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
def meta(key)
|
|
42
|
+
buffer = FFI::MemoryPointer.new(:char, 256)
|
|
43
|
+
length = Cpp.llama_model_meta_val_str(@pointer, key.to_s, buffer, buffer.size)
|
|
52
44
|
|
|
53
|
-
|
|
45
|
+
length.negative? ? nil : buffer.read_string
|
|
46
|
+
end
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
def sampling_defaults
|
|
49
|
+
@sampling_defaults ||= {
|
|
50
|
+
temperature: fetch_meta_float('general.sampling.temp') || DEFAULT_SAMPLING[:temperature],
|
|
51
|
+
top_k: fetch_meta_int('general.sampling.top_k') || DEFAULT_SAMPLING[:top_k],
|
|
52
|
+
top_p: fetch_meta_float('general.sampling.top_p') || DEFAULT_SAMPLING[:top_p],
|
|
53
|
+
min_p: fetch_meta_float('general.sampling.min_p') || DEFAULT_SAMPLING[:min_p]
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def bos_token
|
|
58
|
+
@bos_token ||= token_to_string(Cpp.llama_vocab_bos(vocab))
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
+
def eos_token
|
|
62
|
+
@eos_token ||= token_to_string(Cpp.llama_vocab_eos(vocab))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def generate(prompt, max_tokens: DEFAULT_CONTEXT_LENGTH, temperature: nil, top_k: nil, top_p: nil, min_p: nil,
|
|
66
|
+
seed: nil, system: nil, tools: nil, reasoning: nil, &block)
|
|
61
67
|
init_context(n_ctx: max_tokens) do |ctx|
|
|
62
68
|
ctx.generate(prompt, max_tokens: ctx.n_ctx,
|
|
63
|
-
temperature:, top_k:, top_p:, seed:, system:, min_p:,
|
|
69
|
+
temperature:, top_k:, top_p:, seed:, system:, min_p:, tools:, reasoning:,
|
|
64
70
|
&block)
|
|
65
71
|
end
|
|
66
72
|
end
|
|
@@ -94,11 +100,18 @@ module Rllama
|
|
|
94
100
|
end
|
|
95
101
|
|
|
96
102
|
def close
|
|
103
|
+
if @chat_templates
|
|
104
|
+
Common.chat_templates_free(@chat_templates)
|
|
105
|
+
|
|
106
|
+
@chat_templates = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
97
109
|
Cpp.llama_model_free(@pointer)
|
|
98
110
|
end
|
|
99
111
|
|
|
100
|
-
def init_context(embeddings: false, n_ctx: DEFAULT_CONTEXT_LENGTH, n_batch: 512
|
|
101
|
-
|
|
112
|
+
def init_context(embeddings: false, n_ctx: DEFAULT_CONTEXT_LENGTH, n_batch: 512, system: nil, tools: nil,
|
|
113
|
+
reasoning: false)
|
|
114
|
+
context = Context.new(self, embeddings:, n_ctx:, n_batch:, system:, tools:, reasoning:)
|
|
102
115
|
|
|
103
116
|
if block_given?
|
|
104
117
|
result = yield context
|
|
@@ -115,58 +128,43 @@ module Rllama
|
|
|
115
128
|
init_context(embeddings: true, n_ctx:, n_batch:, &)
|
|
116
129
|
end
|
|
117
130
|
|
|
118
|
-
def
|
|
131
|
+
def apply_chat_template(messages, tools: nil, add_generation_prompt: true, enable_thinking: false)
|
|
119
132
|
raise Error, 'Model does not provide a chat template' if chat_template.nil? || chat_template.empty?
|
|
120
133
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
apply_chat_template_fallback(messages)
|
|
134
|
+
Common.chat_apply(chat_templates, messages:, tools:, add_generation_prompt:, enable_thinking:,
|
|
135
|
+
add_bos: Cpp.llama_vocab_get_add_bos(vocab),
|
|
136
|
+
add_eos: Cpp.llama_vocab_get_add_eos(vocab))
|
|
126
137
|
end
|
|
127
138
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
array_ptr = FFI::MemoryPointer.new(struct_size * count)
|
|
132
|
-
|
|
133
|
-
messages.each_with_index do |m, i|
|
|
134
|
-
struct_ptr = array_ptr + (i * struct_size)
|
|
135
|
-
msg_struct = Cpp::LlamaChatMessage.new(struct_ptr)
|
|
136
|
-
msg_struct[:role] = FFI::MemoryPointer.from_string(m[:role].to_s)
|
|
137
|
-
msg_struct[:content] = FFI::MemoryPointer.from_string(m[:content].to_s)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
needed = Cpp.llama_chat_apply_template(chat_template, array_ptr, count, true, nil, 0)
|
|
141
|
-
|
|
142
|
-
return nil if needed.negative?
|
|
139
|
+
def build_chat_template(messages, tools: nil, add_generation_prompt: true, enable_thinking: false)
|
|
140
|
+
apply_chat_template(messages, tools:, add_generation_prompt:, enable_thinking:)['prompt']
|
|
141
|
+
end
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
written = Cpp.llama_chat_apply_template(chat_template, array_ptr, count, true, buf, needed)
|
|
143
|
+
private
|
|
146
144
|
|
|
147
|
-
|
|
145
|
+
def fetch_meta_float(key)
|
|
146
|
+
value = meta(key)
|
|
148
147
|
|
|
149
|
-
|
|
148
|
+
value && Float(value, exception: false)
|
|
150
149
|
end
|
|
151
150
|
|
|
152
|
-
def
|
|
153
|
-
|
|
151
|
+
def fetch_meta_int(key)
|
|
152
|
+
value = meta(key)
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
return nil unless value
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
Integer(value, exception: false) || Float(value, exception: false)&.to_i
|
|
157
|
+
end
|
|
159
158
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
result << m[:content].to_s
|
|
164
|
-
result << tmpl[:turn_end]
|
|
165
|
-
end
|
|
159
|
+
def chat_templates
|
|
160
|
+
@chat_templates ||= Common.chat_templates_init(@pointer, chat_template, bos_token, eos_token)
|
|
161
|
+
end
|
|
166
162
|
|
|
167
|
-
|
|
163
|
+
def token_to_string(token_id)
|
|
164
|
+
buf = FFI::MemoryPointer.new(:char, 256)
|
|
165
|
+
n = Cpp.llama_token_to_piece(vocab, token_id, buf, buf.size, 0, true)
|
|
168
166
|
|
|
169
|
-
|
|
167
|
+
n.positive? ? buf.read_string(n) : ''
|
|
170
168
|
end
|
|
171
169
|
end
|
|
172
170
|
end
|
data/lib/rllama/version.rb
CHANGED
data/lib/rllama.rb
CHANGED
|
@@ -5,10 +5,11 @@ module Rllama
|
|
|
5
5
|
autoload :Loader, 'rllama/loader'
|
|
6
6
|
autoload :Context, 'rllama/context'
|
|
7
7
|
autoload :Cpp, 'rllama/cpp'
|
|
8
|
+
autoload :Common, 'rllama/common'
|
|
8
9
|
autoload :Cli, 'rllama/cli'
|
|
9
10
|
autoload :VERSION, 'rllama/version'
|
|
10
11
|
|
|
11
|
-
Result = Struct.new(:text, :stats, keyword_init: true)
|
|
12
|
+
Result = Struct.new(:text, :reasoning, :tool_calls, :stats, keyword_init: true)
|
|
12
13
|
Error = Class.new(StandardError)
|
|
13
14
|
|
|
14
15
|
module_function
|