Packages
llama_cpp_ex
0.8.20
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.0
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.3.0
0.2.0
Elixir bindings for llama.cpp — run LLMs locally with Metal, CUDA, Vulkan, or CPU acceleration.
Current section
Files
Jump to
Current section
Files
lib/llama_cpp_ex/server/strategy/batch.ex
defmodule LlamaCppEx.Server.Strategy.Batch do
@moduledoc """
Shared batch-assembly helpers used by the batching strategies.
The strategies (`DecodeMaximal`, `PrefillPriority`, `Balanced`) only differ in
the order and budget split between decode and prefill — the per-slot assembly
of decode tokens and prefill chunks is identical, so it lives here.
## Performance notes
These helpers run on every tick of the server loop, once per active slot, and
the prefill helper runs once per token of every prompt. To keep the loop linear
in the number of entries:
* A running `n_entries` count is threaded through the accumulators instead of
calling `length/1` on the growing `entries` list. Entries are prepended and
reversed once by the caller, so a freshly appended entry's final batch index
is exactly the current `n_entries`.
* Prompt length comes from the cached `slot.n_prompt_tokens` rather than
`length(slot.prompt_tokens)`.
* Prefill chunks are sliced from `slot.prompt_tokens_tuple` (O(1) random
access) rather than `Enum.slice/3` on a list (O(prefill_pos)).
All helpers take and return the 4-tuple `{entries, n_entries, slots, budget}`.
"""
@doc """
Adds one decode token for each generating slot (lowest seq_id first) until the
budget is exhausted. Streams each token piece to the slot's subscriber.
"""
def add_decode_tokens(slots, entries, n_entries, budget, model_ref) do
generating_slots =
slots
|> Enum.filter(fn {_id, slot} ->
slot.state == :generating and slot.pending_token != nil
end)
|> Enum.sort_by(&elem(&1, 0))
Enum.reduce(generating_slots, {entries, n_entries, slots, budget}, fn
{seq_id, _slot}, {entries, n_entries, slots, budget} ->
if budget <= 0 do
{entries, n_entries, slots, budget}
else
slot = slots[seq_id]
token = slot.pending_token
piece = LlamaCppEx.NIF.token_to_piece(model_ref, token)
if slot.stream_pid && slot.stream_ref do
send(slot.stream_pid, {slot.stream_ref, {:token, piece}})
end
slot = %{
slot
| accumulated_pieces: [piece | slot.accumulated_pieces],
batch_idx: n_entries,
tokens_generated: slot.tokens_generated + 1,
generated_token_ids: [token | slot.generated_token_ids]
}
entry = {token, slot.pos, seq_id, true}
slots = Map.put(slots, seq_id, slot)
{[entry | entries], n_entries + 1, slots, budget - 1}
end
end)
end
@doc """
Fills the remaining budget with prefill chunks for each prefilling slot
(lowest seq_id first). Only the last token of a slot's final chunk requests
logits.
"""
def add_prefill_chunks(slots, entries, n_entries, budget, chunk_size) do
prefilling_slots =
slots
|> Enum.filter(fn {_id, slot} -> slot.state == :prefilling end)
|> Enum.sort_by(&elem(&1, 0))
Enum.reduce(prefilling_slots, {entries, n_entries, slots, budget}, fn
{seq_id, _slot}, {entries, n_entries, slots, budget} ->
if budget <= 0 do
{entries, n_entries, slots, budget}
else
slot = slots[seq_id]
remaining = slot.n_prompt_tokens - slot.prefill_pos
chunk_len = min(budget, min(chunk_size, remaining))
is_last_chunk = slot.prefill_pos + chunk_len >= slot.n_prompt_tokens
tuple = slot.prompt_tokens_tuple
# Build this chunk's entries in O(chunk_len): elem/2 is O(1) on a tuple,
# and n_entries gives each entry's final batch index without length/1.
{entries, n_entries, last_batch_idx} =
Enum.reduce(0..(chunk_len - 1)//1, {entries, n_entries, -1}, fn i,
{entries, n_entries,
_last} ->
pos = slot.prefill_pos + i
token = elem(tuple, pos)
logits = is_last_chunk and i == chunk_len - 1
entry = {token, pos, seq_id, logits}
{[entry | entries], n_entries + 1, n_entries}
end)
slot =
if is_last_chunk do
%{slot | batch_idx: last_batch_idx, prefill_pos: slot.prefill_pos + chunk_len}
else
%{slot | batch_idx: -1, prefill_pos: slot.prefill_pos + chunk_len}
end
slots = Map.put(slots, seq_id, slot)
{entries, n_entries, slots, budget - chunk_len}
end
end)
end
end