Standard library · Machine learning

llm

Running Llama and Qwen language models from a GGUF file.

import llm

This package runs large language models of the Llama architecture, such as Llama, Qwen and Mistral, on the processor with no outside dependency. Download the quantised model in GGUF form, read it with LoadGgufModel and make text with Generate.

For more control, Forward gives the logits and a Sampler picks the next token with temperature, top-k, top-p and a repetition penalty. The runnable example builds a small random model so it works without a download.

Types

Tokenizer

struct Tokenizer
The tokenizer: it turns text into indices and back.
NameTypeDescription
kindintThe kind.
tokensVectorThe text of the tokens.
scoresVectorThe scores, for SentencePiece.
indexHashMapThe index of each token.
mergesHashMapThe ranks of the BPE merges.
merge_keysVectorThe merge keys.
bosint = -1The index of the beginning token; -1 means there is none.
eosint = -1The index of the end token.
unkint = 0The index of the unknown token.
add_bosbool = trueAdd the beginning token when encoding.
byte_idsVectorThe token index of each byte.
b2uVectorThe byte to character map of byte level BPE.
llm-Generate.salam
import llm
import tensor

func main:
    mut tokenizer := llm.NewTokenizer(llm.TOK_BPE)
    each text in ["a", "b", "c", "ab"]:
        _ := tokenizer.add_token(text, 0.0)
    end
    tokenizer.add_merge("a", "b")
    tokenizer.finish()
    tokenizer.add_bos = false
    mut ids := tokenizer.encode("abcab")
    println tokenizer.size(), ids.len(), tokenizer.token_text(ids.get(0)), tokenizer.decode(ids)
    println llm.TOK_SPM, llm.ROPE_NORMAL, llm.ROPE_NEOX

    mut config := llm.Config { vocab = tokenizer.size(), dim = 8, layers = 2, heads = 2, kv_heads = 1, ff = 16, ctx = 32 }
    println config.head_dim()
    mut model := llm.RandomModel(config, 7)
    mut cache := llm.NewKVCache(config)
    println model.ok, model.layers.len(), cache.ctx

    mut logits := llm.Forward(model, cache, ids)
    println tensor.ShapeString(logits), cache.len
    mut last := llm.LogitsRow(logits, ids.len() - 1)
    greedy := llm.Greedy(logits, ids.len() - 1)
    println tensor.ShapeString(last), greedy == tensor.ArgMaxAll(last)

    mut sampler := llm.NewSampler(1)
    sampler.temperature = 0.8
    sampler.top_k = 3
    sampler.top_p = 0.9
    sampler.repeat_penalty = 1.1
    mut recent := Vector {} as Vector<int>
    next := sampler.sample(logits, ids.len() - 1, recent)
    println next >= 0 and next < tokenizer.size()

    cache.reset()
    generated := llm.GenerateIds(model, cache, ids, 4, sampler, -1)
    println generated.len()
    cache.reset()
    text := llm.Generate(model, cache, tokenizer, "ab", 3, sampler)
    println text.len() > 0
    tensor.Free(logits)
    tensor.Free(last)
    cache.free()
    model.free()
    tokenizer.free()
end
Output4 3 ab abcab 0 0 1 4 true 2 32 3x4 3 1x4 true true 4 true

add_token

func add_token(text: str, score: f64): int
Adds a token and returns its index.

The example for this item is shown with the ones above: llm-Generate.salam

add_merge

func add_merge(a: str, b: str)
A BPE merge rule, in rank order.

The example for this item is shown with the ones above: llm-Generate.salam

finish

func finish()
Call it once the tokens are added, to ready the tables.

The example for this item is shown with the ones above: llm-Generate.salam

size

func size(): int
The number of tokens.

The example for this item is shown with the ones above: llm-Generate.salam

token_text

func token_text(id: int): str
The text of one token.

The example for this item is shown with the ones above: llm-Generate.salam

encode

func encode(text: str): Vector<int>
Text into indices.

The example for this item is shown with the ones above: llm-Generate.salam

decode

func decode(ids &: Vector<int>): str
Indices into text.

The example for this item is shown with the ones above: llm-Generate.salam

free

func free()
Frees it.

The example for this item is shown with the ones above: llm-Generate.salam

Sampler

struct Sampler
The sampler: it picks the next token from the logits.
NameTypeDescription
temperaturef64 = 1The temperature; lower means a more certain answer.
top_kint = 0Out of the k likeliest tokens only; zero means no limit.
top_pf64 = 1Out of the tokens whose probabilities add up to p only.
repeat_penaltyf64 = 1The penalty for repeating a recent token.
rngtensor.RngThe random generator.

The example for this item is shown with the ones above: llm-Generate.salam

sample

func sample(logits &: tensor.Tensor, row: int, recent &: Vector<int>): int
Picks the next token, with the recent tokens taken into account.

The example for this item is shown with the ones above: llm-Generate.salam

Config

struct Config
The architecture configuration of a model, of the Llama kind.
NameTypeDescription
vocabint = 0The vocabulary size.
dimint = 0The embedding size.
layersint = 0The number of layers.
headsint = 1The number of attention heads.
kv_headsint = 1The number of key and value heads; fewer than heads means grouped attention.
ffint = 0The size of the feed forward layer.
ctxint = 512The greatest text length.
epsf64 = 1e-05The small number for normalisation.
rope_thetaf64 = 10000The base of the rotary embedding.
rope_kindintThe kind of rotary embedding, one of the ROPE_ constants.

The example for this item is shown with the ones above: llm-Generate.salam

head_dim

func head_dim(): int
The size of each attention head.

The example for this item is shown with the ones above: llm-Generate.salam

Layer

struct Layer
The weights of one transformer layer.
NameTypeDescription
attn_normtensor.TensorThe normalisation before the attention.
wqtensor.TensorThe query weight.
wktensor.TensorThe key weight.
wvtensor.TensorThe value weight.
wotensor.TensorThe attention output weight.
ffn_normtensor.TensorThe normalisation before the feed forward layer.
w_gatetensor.TensorThe SwiGLU gate weight.
w_uptensor.TensorThe up weight.
w_downtensor.TensorThe down weight.

The example for this item is shown with the ones above: llm-Generate.salam

free

func free()
Frees the weights.

The example for this item is shown with the ones above: llm-Generate.salam

Model

struct Model
A language model.
NameTypeDescription
cfgConfigThe configuration.
tok_embedtensor.TensorThe token embedding table.
layersVectorThe layers.
out_normtensor.TensorThe final normalisation.
out_headtensor.TensorThe output weight back to the vocabulary.
okbool = falseIt was built or loaded correctly.

The example for this item is shown with the ones above: llm-Generate.salam

free

func free()
Frees the model.

The example for this item is shown with the ones above: llm-Generate.salam

KVCache

struct KVCache
The key value cache: it keeps the keys and values of the earlier tokens so each new token is worked out only once.
NameTypeDescription
kVectorThe keys, one set per layer.
vVectorThe values.
lenint = 0The number of tokens processed.
ctxint = 0The capacity.

The example for this item is shown with the ones above: llm-Generate.salam

reset

func reset()
Empties it for a fresh text.

The example for this item is shown with the ones above: llm-Generate.salam

free

func free()
Frees it.

The example for this item is shown with the ones above: llm-Generate.salam

Constants and variables

TOK_SPM

const TOK_SPM: i32 = 0
The SentencePiece tokenizer, as Llama 2 uses.

The example for this item is shown with the ones above: llm-Generate.salam

TOK_BPE

const TOK_BPE: i32 = 1
The byte level BPE tokenizer, as GPT-2, Llama 3 and Qwen use.

The example for this item is shown with the ones above: llm-Generate.salam

ROPE_NORMAL

const ROPE_NORMAL: i32 = 0
The Llama rotary embedding.

The example for this item is shown with the ones above: llm-Generate.salam

ROPE_NEOX

const ROPE_NEOX: i32 = 1
The GPT-NeoX rotary embedding, for Qwen and the like.

The example for this item is shown with the ones above: llm-Generate.salam

Functions

NewTokenizer

func NewTokenizer(kind: int): Tokenizer
An empty tokenizer of one kind.

The example for this item is shown with the ones above: llm-Generate.salam

NewSampler

func NewSampler(seed: i64): Sampler
A sampler from a seed.

The example for this item is shown with the ones above: llm-Generate.salam

Greedy

func Greedy(logits &: tensor.Tensor, row: int): int
The likeliest token.

The example for this item is shown with the ones above: llm-Generate.salam

GenerateIds

func GenerateIds(m &: Model, cache &: KVCache, prompt_ids &: Vector<int>, max_new: int, sp &: Sampler, eos: int): Vector<int>
Generates up to n new tokens after the starting indices; it stops at the end token.

The example for this item is shown with the ones above: llm-Generate.salam

Generate

func Generate(m &: Model, cache &: KVCache, tok &: Tokenizer, prompt: str, max_new: int, sp &: Sampler): str
Carries on from the starting text and returns the new text.

The example for this item is shown with the ones above: llm-Generate.salam

Forward

func Forward(m &: Model, cache &: KVCache, tokens &: Vector<int>): tensor.Tensor
Passes the tokens through the model and returns the logits of each position; the cache is updated.

The example for this item is shown with the ones above: llm-Generate.salam

LogitsRow

func LogitsRow(logits &: tensor.Tensor, row: int): tensor.Tensor
The logits of one position.

The example for this item is shown with the ones above: llm-Generate.salam

ConfigFromGguf

func ConfigFromGguf(g &: gguf.Gguf): Config
The configuration from GGUF metadata.
llm-Load.salam
import llm
import gguf

func main:
    path := "models/qwen2.5-0.5b-instruct-q8_0.gguf"
    mut model := llm.Model {}
    mut tokenizer := llm.NewTokenizer(llm.TOK_BPE)
    if not llm.LoadGgufModel(path, model, tokenizer):
        println "the model was not loaded:", path
        ret
    end
    mut cache := llm.NewKVCache(model.cfg)
    mut sampler := llm.NewSampler(42)
    sampler.temperature = 0.7
    println llm.Generate(model, cache, tokenizer, "The capital of France", 20, sampler)

    mut good := false
    mut file := gguf.LoadGguf(path, good)
    config := llm.ConfigFromGguf(file)
    println config.vocab, config.dim, config.layers, config.heads, config.kv_heads, config.ff, config.ctx, config.eps, config.rope_theta, config.rope_kind
    mut model2 := llm.ModelFromGguf(file)
    mut tokenizer2 := llm.TokenizerFromGguf(file)
    println tokenizer2.bos, tokenizer2.eos, tokenizer2.unk
    model2.free()
    tokenizer2.free()
    file.free()
    cache.free()
    model.free()
    tokenizer.free()
end

ModelFromGguf

func ModelFromGguf(g &: gguf.Gguf): Model
Loads the weights from a GGUF file.

The example for this item is shown with the ones above: llm-Load.salam

TokenizerFromGguf

func TokenizerFromGguf(g &: gguf.Gguf): Tokenizer
Builds the tokenizer from the vocabulary in the file.

The example for this item is shown with the ones above: llm-Load.salam

LoadGgufModel

func LoadGgufModel(path: str, m &: Model, t &: Tokenizer): bool
Loads the model and the tokenizer from a .gguf file.

The example for this item is shown with the ones above: llm-Load.salam

NewKVCache

func NewKVCache(cfg &: Config): KVCache
A cache for a configuration.

The example for this item is shown with the ones above: llm-Generate.salam

RandomModel

func RandomModel(cfg &: Config, seed: i64): Model
A model with random weights; for testing without a download.

The example for this item is shown with the ones above: llm-Generate.salam