llama

llama.cpp bindings for Moonbit

llama
moon add Kaida-Amethyst/llama@0.2.0
Download zip
Version
0.2.0
License
Apache-2.0
Last updated
6 days ago
Downloads
4
README

#llama.mbt

A lightweight MoonBit binding for the llama.cpp C API. Version 0.1.0 was the first Moon Registry release; version 0.2.0 adds greedy and random-preset text completion.

#Quick start

Requirements: Apple Silicon, macOS 14 or later, MoonBit's native toolchain, and a local GGUF model. Users do not need the llama.cpp submodule, CMake, Python, a JVM, or a system installation of llama.cpp. On the first native build, build.js downloads and verifies the pinned prebuilt static libraries; later builds reuse the shared Moon cache.

The repository includes a script for downloading a tiny model fixture. Omitting the final argument selects greedy sampling; random selects the random preset with seed 42 in the runnable example.

./scripts/fetch-test-model.sh moon run main --target native -- target/test-models/stories15M-q4_0.gguf moon run main --target native -- target/test-models/stories15M-q4_0.gguf random

The two minimal option configurations are:

///|
let greedy_options = @llama.GenerationOptions::new(max_tokens=32)

///|
let random_options = @llama.GenerationOptions::new(
max_tokens=32,
strategy=@llama.SamplingStrategy::random(seed=42U),
)

Pass either value to session.complete(prompt, options~). See main/main.mbt for a runnable program that loads a model, creates a Session, generates text, and reads the resulting Completion.

#Sampling behavior

SamplingStrategy::random() uses temperature 0.8, top-k 40, and top-p 0.95. Its fixed pipeline is top-k → top-p → temperature → distribution; the first version does not expose arbitrary sampler chains.

Input values are normalized rather than rejected:

  • Negative temperature becomes 0.0; NaN uses the default 0.8.
  • Negative top-k becomes 0.
  • Top-p is clamped to [0.0, 1.0]; NaN uses the default 0.95.
  • Omitting seed, or passing the maximum UInt value (0xFFFFFFFF), requests llama.cpp's non-fixed seed behavior.

Each complete call creates and releases its own sampler. A fixed seed therefore restarts the RNG for every call, but does not reset the Session context. It is only expected to reproduce a result when the model, prebuilt artifact, Session context, prompt, sampling options, backend, and hardware are also fixed; it is not a cross-version or cross-platform guarantee.

#0.2.0 compatibility

SamplingStrategy is now constructed through SamplingStrategy::greedy() and SamplingStrategy::random(...). Code that directly constructed or matched the 0.1.0 Greedy variant must migrate to these methods. SamplingStrategy, Completion, FinishReason, and the public error types also no longer derive Eq; test their stable fields or use is pattern checks instead.

#State and ownership

  • Copying a Model shares its owner and model weights. Copying a Session shares the same mutable context and KV cache; it does not clone conversation state. A Session is not promised to be safe for concurrent use.
  • A successful complete appends the prompt and generated non-EOG tokens to the Session. The returned Completion.text contains only text generated by that call. Call reset to deliberately start from an empty context.
  • Validation and tokenization failures before native decode leave the Session unchanged. Once decode begins, an error may leave its state uncertain; call reset before reuse. A later call on such a Session raises SessionNeedsReset until it is reset.
  • FinishReason describes a successful normal stop. Generation failures use MoonBit's error mechanism instead.

Version 0.2.0 supports synchronous, single-sequence completion with greedy and random presets on macOS ARM64. Streaming, async APIs, Chat abstractions, custom sampler chains, and other platforms are not implemented yet. The pinned artifact and upgrade policy are documented in docs/prebuilt-artifacts.md.

#中文

llama.mbt 是 llama.cpp C API 的轻量 MoonBit binding。0.1.0 是首个发布到 Moon Registry 的版本;0.2.0 增加了 greedy 与 random preset 文本补全。

#快速开始

运行环境需要 Apple Silicon、macOS 14 或更高版本、MoonBit native toolchain, 以及一个本地 GGUF 模型。普通用户不需要 llama.cpp submodule、CMake、Python、 JVM,也不需要预先安装系统 libllama。首次 native 构建时,build.js 会下载并 校验固定版本的预编译静态库,之后的项目会复用 Moon 共享缓存。

仓库中的脚本可以下载一个小型模型 fixture。运行上面的第一条 moon run 命令会 使用默认 greedy;末尾传入 random 时,runnable example 会使用 seed 42 的 random preset。两种方式分别对应上面的 greedy_optionsrandom_options 完整程序见 main/main.mbt

#Sampling 行为

SamplingStrategy::random() 的默认参数是 temperature 0.8、top-k 40 top-p 0.95。内部 pipeline 固定为 top-k → top-p → temperature → distribution,第一版不开放任意 sampler chain。

非法或越界输入不会抛出配置错误,而是按以下规则规范化:

  • 负 temperature 变为 0.0NaN 回落到默认值 0.8
  • 负 top-k 变为 0
  • top-p 被限制到 [0.0, 1.0]NaN 回落到默认值 0.95
  • 省略 seed 或传入 UInt 最大值(0xFFFFFFFF),都表示采用 llama.cpp 的 非固定 seed 行为。

每次 complete 都会独立创建并释放 sampler。固定 seed 会在每次调用开始时重置 RNG,但不会重置 Session context。只有模型、预构建 artifact、Session context、 prompt、sampling 参数、backend 与硬件均固定时,才预期得到可重复结果;这不是 跨版本或跨平台的保证。

#0.2.0 兼容性

SamplingStrategy 现在通过 SamplingStrategy::greedy() SamplingStrategy::random(...) 构造。0.1.0 中直接构造或匹配 Greedy 变体的 代码需要迁移到这些方法。SamplingStrategyCompletionFinishReason 与公开 错误类型也不再派生 Eq;测试时应比较稳定字段,或使用 is pattern 检查。

#状态与所有权

  • 复制 Model 只共享 owner 和模型权重。复制 Session 会共享同一个可变 context 与 KV cache,并不会复制会话状态;Session 不承诺并发安全。
  • complete 成功后,prompt 和生成的非 EOG token 会追加到当前 Session;返回的 Completion.text 只包含本次新生成的文本。需要从空 context 重新开始时调用 reset
  • native decode 开始前的校验和 tokenize 错误不会修改 Session;decode 开始后若 发生错误,继续使用前应调用 reset。待恢复的 Session 会抛出 SessionNeedsReset,直到完成 reset。
  • FinishReason 只描述成功结果的正常停止原因;生成失败使用 MoonBit 错误机制。

0.2.0 只支持 macOS ARM64 上同步、单序列的 greedy 与 random preset 补全;尚未 实现 streaming、async API、Chat 抽象、任意 sampler chain 或其他平台。预构建 产物与上游升级策略见 docs/prebuilt-artifacts.md

#
GenerationError

pub(all) suberror GenerationError {
SessionNeedsReset
VocabularyUnavailable
EmptyPrompt
PromptExceedsContext(Int, UInt)
TokenizationFailed(Int)
SamplerCreateFailed
DecodeFailed(Int)
TokenToPieceFailed(Int, Int)
} derive(
Debug
)

同步文本生成在返回 Completion 前可能产生的错误。

这些错误表示没有可用的正常生成结果,不属于 FinishReason。decode 已经开始后 发生的错误可能修改 Session;具体恢复规则由 Session 的状态协议负责。

#
LlamaError

pub(all) suberror LlamaError {
ModelLoadFailed(String)
ContextCreateFailed(UInt)
} derive(
Debug
)

llama.mbt 高层 API 在模型与 Session 生命周期阶段可能产生的错误。

#
Completion

pub struct Completion {
text : String
prompt_tokens : Int
generated_tokens : Int
finish_reason : FinishReason
} derive(
Debug
)

一次成功文本生成的结构化结果。

text 只包含本次新生成的文本;两个 token 计数也只对应本次调用。 struct 构造器不公开,调用者可以直接读取各字段。

#
FinishReason

pub(all) enum FinishReason {
EndOfGeneration
MaxTokens
ContextFull
} derive(
Debug
)

一次成功生成正常结束的原因。

tokenize、decode 等失败通过 MoonBit 错误机制传播,不属于 FinishReason

#
GenerationOptions

pub struct GenerationOptions {
// private fields
}

一次文本生成的配置。

字段保持私有,调用者通过 GenerationOptions::new 构造完整配置。

#
GenerationOptions::new

fn GenerationOptions::new(max_tokens~ : Int, strategy? : SamplingStrategy) -> GenerationOptions

创建一次文本生成的配置。

负数会统一规范化为 -1,表示不设置由调用者指定的 token 数量上限;0 表示不生成新 token;正数表示最多生成对应数量的新 token。

#
Model

pub struct Model {
// private fields
}

已加载模型的共享 owner。

复制 Model 只会共享同一个 owner,不会复制模型权重。最后一个关联引用消失后, owner 会自动释放原生 model;调用者不需要显式 free

#
Model::load

fn Model::load(path : StringView) -> Model raise LlamaError

加载本地 GGUF 模型。

#
Model::new_session

fn Model::new_session(self : Model, context_size~ : UInt) -> Session raise LlamaError

创建一个单序列、有状态的 Session。

context_size=0U 时由 llama.cpp 选择默认容量。返回的 Session 会保留当前 Model 的 owner,因此即使原来的 Model binding 离开作用域,原生 model 仍会保持有效。

#
SamplingStrategy

pub enum SamplingStrategy {
Greedy
Random(seed~ : UInt?, temperature~ : Double, top_k~ : Int, top_p~ : Double)
} derive(
Debug
)

一次文本生成所采用的 sampling strategy。

具体变体留在包内,调用者通过 SamplingStrategy::greedy SamplingStrategy::random 构造策略。

#
SamplingStrategy::greedy

创建 greedy sampling strategy。

#
SamplingStrategy::random

fn SamplingStrategy::random(seed? : UInt, temperature? : Double, top_k? : Int, top_p? : Double) -> SamplingStrategy

创建第一版 random sampling strategy。

seed=None 表示使用非固定 seed;llama.cpp 将 0xFFFFFFFFU 保留为同一 语义,因此显式传入 @uint.MAX_VALUE 也会规范化为 None。负 temperature 规范化为 0.0,负 top-k 规范化为 0,top-p 限制到 [0.0, 1.0] temperature 或 top-p 为 NaN 时分别回落到默认值 0.80.95

#
Session

pub struct Session {
// private fields
}

单序列、有状态的推理 Session。

复制 Session 会共享同一个 context 和会话状态,不会克隆 KV cache,也不承诺 并发安全。最后一个引用消失后,owner 会自动释放原生 context。

#
Session::complete

fn Session::complete(self : Session, prompt : StringView, options~ : GenerationOptions) -> Completion raise GenerationError

将 prompt 追加到当前 Session,并按本次 SamplingStrategy 同步生成文本。

prompt 只在本次调用期间读取;返回文本只包含本次新生成的内容。成功返回时, prompt 和所有非 EOG 生成 token 都保留在 Session 中,供下一次调用继续使用。 sampler 只属于本次调用;固定 seed 会在每次调用开始时重新初始化 RNG。 decode 已经开始后若发生错误,后续调用会抛出 SessionNeedsReset,直到用户调用 Session::reset 明确丢弃无法确认的部分状态。

#
Session::get_context_size

fn Session::get_context_size(self : Session) -> UInt

返回 llama.cpp 实际采用的 context 容量。

#
Session::reset

fn Session::reset(self : Session) -> Unit

清空 Session 当前持有的 context memory。

reset 后同一个 Session 可以从空白上下文重新开始,并从 NeedsReset 恢复为 Ready;重复调用也是安全的。