moonbit-mirostat

    Model-independent Mirostat adaptive text sampling for MoonBit

    llm
    sampling
    mirostat
    decoding
    Download zip
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    7 days ago
    Downloads
    43

    No dependencies

    #moonbit-mirostat

    一个与模型无关的 MoonBit Mirostat 自适应采样实现。调用方每生成一个 token,传入一行 logits 和一个 [0, 1) 均匀随机数。库返回选中的 token、它在原始模型分布下的概率与惊讶度、保留的候选数量,以及更新后的反馈状态。

    本包实现了 Mirostat 论文中的两个算法:V1 估计 Zipf 指数并自适应选择 top-k;V2 保留模型惊讶度不超过当前 mu 的 token。反馈规则为 mu := mu - eta * (observed_surprise - tau)。tau、mu 和惊讶度均以 bit 为单位。

    #适用场景

    • MoonBit 文本生成循环可以控制生成 token 的惊讶度,而不必为每一步固定 top-k 或 top-p 阈值。
    • WebAssembly 应用可以在模型推理后于本地完成采样,不必通过 JavaScript 执行 token 选择。
    • 解码实验可以将相同的 logits 和随机数依次交给 V1、V2,比较保留词表和惊讶度轨迹。

    本包不运行语言模型、不进行分词、不下载模型权重,也不管理随机数生成器。logits 和随机数由调用方提供。本包不承诺与某个模型运行时完全等价:运行时可能会在 Mirostat 前应用重复惩罚、语法掩码、温度等变换,这些变换会改变输入分布。

    #安装与最小使用

    需要 MoonBit 编译器 moonc >= 0.10.16。当前 CI 在 Linux、macOS、Windows 上固定使用 moonc v0.10.18+6c83a7f62,Node 桥接测试使用 Node.js 22。

    在已有 MoonBit 项目中安装已发布的库:

    moon add xlyyyyyy112/moonbit-mirostat@0.1.2

    在使用它的包的 moon.pkg 中加入:

    ///|
    import {
    "xlyyyyyy112/moonbit-mirostat" @mirostat,
    }

    在可执行包的 main 函数中调用:

    ///|
    fn main {
    let config = @mirostat.Config::new(3.0, eta=0.1).unwrap()
    let sampler = @mirostat.Sampler::new(config, version=@mirostat.Version::v2())
    let step = sampler.sample([4.0, 2.0, 1.0], 0.42).unwrap()
    println(step.token())
    }

    库的核心算法和 jsonio 支持 wasm、wasm-gc、JavaScript 和 native。 bridge 是给 Node/Hugging Face 使用的 CommonJS 导出库,只构建 JavaScript 目标; 它不是原生命令行程序。

    #从源码构建与运行

    git clone https://github.com/xlyyyyyy112/moonbit-mirostat.git cd moonbit-mirostat moon update

    仓库示例使用源码中的库,下面的 cmd/main 不需要模型权重、Python 或网络服务。

    moon check --deny-warn moon build --deny-warn moon test --deny-warn moon run cmd/main moon run cmd/experiment moon run cmd/generate moon run cmd/batch moon run cmd/weights moon run cmd/process moon run cmd/sweep moon run cmd/json

    cmd/main 会打印 V1/V2 各五步的 token、候选数量和反馈状态。完整 CI 复现命令为:

    moon version --all moon check --target all --deny-warn moon build --target all --deny-warn moon test --target all --deny-warn moon test --release --deny-warn moon build --target js --release --deny-warn node scripts/test_hf_bridge.cjs moon fmt --check moon info git diff --exit-code

    native 构建需要平台 C 编译器及链接器;JS 桥接测试需要 Node.js。 格式和生成接口应使用 CI 的固定工具链更新,避免不同版本造成格式差异。

    • cmd/main:为每个算法版本打印五次可复现的采样结果。
    • cmd/experiment:用相同的静态 Zipf 分布和随机种子运行两个版本各 1000 次,并输出 CSV 汇总指标。这是确定性的算法测试样例,不是语言模型质量测试。
    • cmd/generate:用小型确定性模型样例演示上下文回调和结束 token。
    • cmd/batch、cmd/weights、cmd/process:分别演示原子批处理、权重输入和显式 logits 预处理。
    • cmd/sweep:在同一受控样例上评估多个目标惊讶度。
    • cmd/json:打印供 WebAssembly 宿主使用的 JSON 数据格式。

    典型接入方式是为每个生成会话创建一个 sampler:

    let config = @mirostat.Config::new(3.0, eta=0.1).unwrap()
    let sampler = @mirostat.Sampler::new(config, version=@mirostat.Version::v2())
    let step = sampler.sample([4.0, 2.0, 1.0], 0.42).unwrap()
    println(step.token())

    传入的随机数必须是有限值且位于 [0, 1)。模型 logits 至少要有一个有限值。负无穷表示屏蔽该 token;NaN 和正无穷会报错。token 索引从零开始,并保持输入 logits 的顺序。概率相同时,较小的 token 索引排在前面,因此固定输入和随机数会得到可复现的结果。reset() 会恢复初始 mu 和步数。replay() 接收对齐的 logits 行与随机数数组,summarize() 汇总平均惊讶度、候选数量和相对目标值的绝对误差。

    若模型后端提供非负权重,可调用 sample_weights(),本包会对权重归一化。preview() 和 preview_weights() 可在不抽样、不改变状态的情况下查看候选数量、保留的模型概率质量和期望惊讶度。ParkMiller 为示例提供可移植且可复现的随机数生成器;sample_with_rng() 会自动从中取随机数。生产环境也可以继续传入宿主自己的随机数。

    checkpoint() 和 restore() 可用于需要回滚的生成分支;fork() 会以相同反馈状态创建独立会话。replay_atomic() 在批次中的某一行失败时恢复状态,replay() 则保留此前成功的步骤。这两个函数都不管理调用方的随机数来源。

    generate() 使用生成的 token 历史和可选结束 token 驱动上下文相关的模型回调。若实验中反复使用固定分布,PreparedRow 会缓存 softmax 和完整排序,sample_prepared() 可重复使用缓存。TraceAccumulator 在线计算汇总指标,无需保留整条轨迹。

    模型服务在一个推理批次中解码多个请求时,可以调用 sample_batch(),传入对齐的 sampler、logits 行和随机数数组。sample_batch_atomic() 会在任一行失败时恢复所有 sampler。每个数组位置应对应不同的生成会话。

    #接入宿主运行时

    如果宿主负责执行类别抽样,可调用 candidates() 获取 token ID、原始概率和归一化后的候选概率。选定 token 后,再调用 observe_token() 验证该 token 是否属于当前 Mirostat 候选集,并更新反馈状态。适用于模型运行时已经管理随机数生成器或实现融合采样内核的情况。

    jsonio 包基于同一套公开 API 提供精简 JSON 协议:init_json 创建初始可移植状态;session_json 根据下一步 logits 行及之前的 state_mu、state_steps 处理一个采样步骤;batch_json 原子处理多个独立会话;preview_json 查看候选集但不消耗随机数;weight_json 为返回非负权重的后端提供一次性采样接口。这些函数不执行模型推理,也不访问文件或网络。

    #真实模型接入(CPU)

    scripts/run_hf_mirostat.py 将 Hugging Face 因果语言模型接入 MoonBit sampler。Transformers 计算每一步的 logits;一个常驻 Node 进程调用编译为 JavaScript 的 MoonBit jsonio.session_json;生成器再将选出的 token 传回模型。每个请求使用一个生成会话和一条随机数流。目前此接入示例尚未实现批量解码。

    已验证的模型是公开的 sshleifer/tiny-gpt2,固定版本为 5f91d94bd9cd7190a9f3216ff93cd1dd95f2c7be。Transformers 通过 LogitsProcessor 暴露下一 token 的分数,适配器将分数发送给常驻的 Node/MoonBit 桥接进程,再将选出的 token 返回生成流程。这验证了 sampler 可以接入真实的 Hugging Face 生成循环。该微型模型能力有限,不能据此声称生成文本质量良好或大型模型性能优越。

    在 Windows 上,可在仓库之外创建独立 Python 环境并下载固定版本的模型(默认路径使用 E 盘)。此配置会复用系统 Python 中的 CPU 版 PyTorch;创建带 --system-site-packages 的虚拟环境前,运行机器的系统 Python 中必须已安装 torch:

    py -3.12 -m venv --system-site-packages E:\moonbit-mirostat-venv E:\moonbit-mirostat-venv\Scripts\python.exe -m pip install -r scripts\requirements-hf.txt $env:HF_HOME = 'E:\moonbit-mirostat\hf-cache' E:\moonbit-mirostat-venv\Scripts\python.exe scripts\download_hf_model.py moon build --target js --release

    使用 Mirostat V2 生成短文本;也可以将 --version v2 改为 --version v1:

    E:\moonbit-mirostat-venv\Scripts\python.exe scripts\run_hf_mirostat.py ` --prompt 'Explain why stable sorting is useful.' --version v2 ` --max-new-tokens 48 --seed 7 --report E:\moonbit-mirostat\runs\v2.json

    同一运行脚本也支持 top-p 基线,例如:

    E:\moonbit-mirostat-venv\Scripts\python.exe scripts\run_hf_mirostat.py ` --prompt 'Explain why stable sorting is useful.' --sampler top-p ` --temperature 0.8 --top-p 0.9 --max-new-tokens 48 --seed 7 ` --report E:\moonbit-mirostat\runs\top-p.json

    JSON 报告会记录基线温度和 --top-p。比较实验时,应使用相同的模型版本、提示词、随机种子、线程数和 token 上限。报告包含生成文本、token ID、逐步惊讶度和候选数量、每秒生成 token 数,以及 MoonBit 桥接耗时。桥接目前每一步都通过 JSON 发送完整的 float32 logits 行,因此会单独测量这部分开销;这是实验性接入方式,并非低延迟生产适配器。运行器目前仅支持 batch size 为 1,并要求 logits 为有限值。本项目不声称目标惊讶度总能等于观测均值,也不声称 Mirostat 一定提升文本质量。

    模型下载脚本会在已下载权重旁记录模型 ID 和精确版本;仓库不包含模型权重。CPU 解码不需要 CUDA GPU 或付费推理 API。实际吞吐量和桥接开销见中文验证记录 docs/model-integration-validation.md,其中也列出了该测试样例的局限。

    针对这一接入示例,请分别报告每秒生成 token 数和 MoonBit 桥接的每 token 毫秒数。至少运行三次并比较中位数,每次使用相同的模型版本、提示词、随机种子、线程数和 token 上限。由于 JSON 桥接耗时不等于模型推理耗时,两项指标应分别报告。功能验收条件是每次运行都生成指定数量且 ID 在有效范围内的 token;Mirostat 运行还应为每个生成 token 记录一条轨迹。项目不设与硬件无关的速度门槛,也不把该微型模型作为文本质量测试。

    若要排除模型推理开销,单独测试 JS 桥接和 sampler,可先构建 JS 目标,再运行 node scripts/bench_sampling_bridge.cjs。测试使用固定的 50,257 个 token 的 logits 行,并报告多轮调用的中位耗时;V2 优化结果和测试限制记录在中文验证文档中。

    #验证范围

    测试覆盖稳定 softmax、屏蔽 logits、归一化、候选选择、V1 和 V2 反馈、状态回滚、批处理原子性、JSON 解码、固定种子回放、合成 Zipf 分布漂移,以及 WebAssembly 兼容执行。合成实验用于验证反馈循环在相应测试分布下能否接近可行的目标惊讶度,不代表文本质量、模型质量或特定词表规模下的性能结论。

    #数值与状态行为

    Softmax 会先减去最大 logit 再执行指数运算。Zipf 斜率估计器使用前 m 个排序概率之间的相邻值;对于平坦分布或下溢分布,会使用较小的正数作为斜率下限。V1 会将估算出的 k 限制在词表范围内。V2 至少保留概率最高的 token,即使它的惊讶度超过 mu。所选 token 的惊讶度以截断和重新归一化之前的原始 softmax 概率计算。无效 logits 行或随机数会返回错误,且不改变 sampler 状态。在 replay 中,如果后续行失败,之前成功处理的行仍会保留。

    V1 使用有界堆检查概率最高的 m 个 token,再选择自适应 top-k 候选。V2 扫描整个词表,并仅对低于惊讶度阈值的 token 排序。两种算法每一步仍需检查每个 logit。对于大词表、低延迟解码场景,应使用目标模型和参数对本包进行基准测试。

    为便于受控比较,sample_top_k、sample_top_p 和 sample_temperature 使用相同的稳定概率计算和抽样函数。其他对比采样器还包括 min-p、epsilon 和 locally typical sampling。调用方可以在 Mirostat 前应用 mask_logits、bias_logits 或 penalize_repetition;这些变换是显式执行的,因此报告的惊讶度对应变换后的模型分布。entropy、prefix_mass、expected_prefix_surprise 和 perplexity 可用于比较分布及观测轨迹。

    #来源与许可证

    这是依据 Sourya Basu 等人的论文 Mirostat: A Neural Text Decoding Algorithm That Directly Controls Perplexity(ICLR 2021)独立实现的 MoonBit 项目。作者的 Python 研究代码用于参考术语和算法范围;本仓库未复制其源文件。上游代码采用 MIT 许可证。本 MoonBit 项目采用 Apache-2.0 许可证,详见 LICENSE。

    CandidateSet

    pub struct CandidateSet {
    tokens : Array[Int]
    conditional : Array[Double]
    original : Array[Double]
    mu : Double
    } derive(
    Debug
    )

    The current adaptive candidate distribution for an external categorical sampler. Arrays align by index: tokens[i] has conditional[i] probability after truncation and original[i] probability before truncation.

    CandidateSet::conditional

    fn CandidateSet::conditional(self : CandidateSet) -> Array[Double]

    CandidateSet::conditional_probability

    fn CandidateSet::conditional_probability(self : CandidateSet, token : Int) -> Double?

    Conditional probability after Mirostat truncation, or None if this token is not in the current candidate set.

    CandidateSet::mu

    fn CandidateSet::mu(self : CandidateSet) -> Double

    CandidateSet::original

    fn CandidateSet::original(self : CandidateSet) -> Array[Double]

    CandidateSet::original_probability

    fn CandidateSet::original_probability(self : CandidateSet, token : Int) -> Double?

    Probability under the original model distribution, or None if this token is not in the current candidate set.

    CandidateSet::position

    fn CandidateSet::position(self : CandidateSet, token : Int) -> Int?

    Return the candidate-list position for a vocabulary token. This position indexes the arrays returned by conditional() and original().

    CandidateSet::tokens

    fn CandidateSet::tokens(self : CandidateSet) -> Array[Int]

    Checkpoint

    pub struct Checkpoint {
    config : Config
    version : Version
    mu : Double
    steps : Int
    } derive(Eq,
    Debug
    )

    A snapshot of one sampler's feedback state and configuration. A checkpoint can be restored only to a sampler with the same configuration and variant.

    Checkpoint::equal

    fn Checkpoint::equal(Checkpoint, Checkpoint) -> Bool

    Checkpoint::mu

    fn Checkpoint::mu(self : Checkpoint) -> Double

    Checkpoint::not_equal

    fn Checkpoint::not_equal(x : Checkpoint, y : Checkpoint) -> Bool

    Checkpoint::steps

    fn Checkpoint::steps(self : Checkpoint) -> Int

    Config

    pub struct Config {
    tau : Double
    eta : Double
    initial_mu : Double
    m : Int
    } derive(Eq,
    Debug
    )

    Mirostat feedback configuration. Tau and mu are measured in bits.

    Config::equal

    fn Config::equal(Config, Config) -> Bool

    Config::eta

    fn Config::eta(self : Config) -> Double

    Config::initial_mu

    fn Config::initial_mu(self : Config) -> Double

    Config::m

    fn Config::m(self : Config) -> Int

    Config::new

    fn Config::new(tau : Double, eta? : Double, initial_mu? : Double, m? : Int) -> Result[Config, SamplingError]

    Config::not_equal

    fn Config::not_equal(x : Config, y : Config) -> Bool

    Config::tau

    fn Config::tau(self : Config) -> Double

    Config::to_repr

    Generation

    pub struct Generation {
    tokens : Array[Int]
    steps : Array[Step]
    reason : StopReason
    } derive(Eq,
    Debug
    )

    Generation::equal

    fn Generation::equal(Generation, Generation) -> Bool

    Generation::not_equal

    fn Generation::not_equal(x : Generation, y : Generation) -> Bool

    Generation::reason

    fn Generation::reason(self : Generation) -> StopReason

    Generation::steps

    fn Generation::steps(self : Generation) -> Array[Step]

    Generation::tokens

    fn Generation::tokens(self : Generation) -> Array[Int]

    ParkMiller

    pub struct ParkMiller {
    state : Int
    } derive(
    Debug
    )

    A small reproducible RNG for examples, replay fixtures, and portable sampling. Applications may supply uniforms from their own RNG instead.

    ParkMiller::new

    fn ParkMiller::new(seed : Int) -> Result[ParkMiller, SamplingError]

    ParkMiller::next_int

    fn ParkMiller::next_int(self : ParkMiller) -> Int

    Schrage's method computes (16807 * state) mod (2^31 - 1) without an overflowing 32-bit multiplication.

    ParkMiller::next_uniform

    fn ParkMiller::next_uniform(self : ParkMiller) -> Double

    The result lies strictly inside (0, 1), which satisfies the sampler's required [0, 1) interval.

    ParkMiller::state

    fn ParkMiller::state(self : ParkMiller) -> Int

    PreparedRow

    pub struct PreparedRow {
    probabilities : Array[Double]
    order : Array[Int]
    } derive(
    Debug
    )

    Immutable-by-convention model row prepared for repeated draws. The probabilities and complete rank order are computed only once.

    PreparedRow::from_logits

    fn PreparedRow::from_logits(logits : Array[Double]) -> Result[PreparedRow, SamplingError]

    PreparedRow::from_weights

    fn PreparedRow::from_weights(weights : Array[Double]) -> Result[PreparedRow, SamplingError]

    PreparedRow::probability

    fn PreparedRow::probability(self : PreparedRow, token : Int) -> Result[Double, SamplingError]

    PreparedRow::vocabulary

    fn PreparedRow::vocabulary(self : PreparedRow) -> Int

    Preview

    pub struct Preview {
    vocabulary : Int
    kept_tokens : Int
    retained_mass : Double
    expected_surprise : Double
    mu : Double
    } derive(Eq,
    Debug
    )

    Candidate-set diagnostics computed without drawing a token or updating mu.

    Preview::equal

    fn Preview::equal(Preview, Preview) -> Bool

    Preview::expected_surprise

    fn Preview::expected_surprise(self : Preview) -> Double

    Preview::kept_tokens

    fn Preview::kept_tokens(self : Preview) -> Int

    Preview::mu

    fn Preview::mu(self : Preview) -> Double

    Preview::not_equal

    fn Preview::not_equal(x : Preview, y : Preview) -> Bool

    Preview::retained_mass

    fn Preview::retained_mass(self : Preview) -> Double

    Preview::to_repr

    Preview::vocabulary

    fn Preview::vocabulary(self : Preview) -> Int

    Sampler

    pub struct Sampler {
    config : Config
    version : Version
    mu : Double
    steps : Int
    } derive(
    Debug
    )

    A session owns only feedback state; the model owns logits and the caller owns RNG.

    Sampler::candidates

    fn Sampler::candidates(self : Sampler, logits : Array[Double]) -> Result[CandidateSet, SamplingError]

    Sampler::candidates_weights

    fn Sampler::candidates_weights(self : Sampler, weights : Array[Double]) -> Result[CandidateSet, SamplingError]

    Sampler::checkpoint

    fn Sampler::checkpoint(self : Sampler) -> Checkpoint

    Sampler::fork

    fn Sampler::fork(self : Sampler) -> Sampler

    Copy a sampler's feedback state into a separate mutable session. This supports branching generation without sharing subsequent feedback.

    Sampler::from_state

    fn Sampler::from_state(config : Config, version : Version, state : SamplerState) -> Sampler

    Sampler::mu

    fn Sampler::mu(self : Sampler) -> Double

    Sampler::new

    fn Sampler::new(config : Config, version? : Version) -> Sampler

    Sampler::observe_prepared_token

    fn Sampler::observe_prepared_token(self : Sampler, row : PreparedRow, token : Int) -> Result[Step, SamplingError]

    Validate an externally chosen token against a precomputed model row.

    Sampler::observe_token

    fn Sampler::observe_token(self : Sampler, logits : Array[Double], token : Int) -> Result[Step, SamplingError]

    Advance Mirostat feedback after an external sampler chooses a token from this library's candidate set. The token is checked against the same adaptive truncation that sample would use. This lets a host own the RNG and categorical draw while MoonBit maintains the feedback state.

    Sampler::observe_weighted_token

    fn Sampler::observe_weighted_token(self : Sampler, weights : Array[Double], token : Int) -> Result[Step, SamplingError]

    Sampler::preview

    fn Sampler::preview(self : Sampler, logits : Array[Double]) -> Result[Preview, SamplingError]

    Inspect the next candidate set on a logits row without consuming a draw.

    Sampler::preview_prepared

    fn Sampler::preview_prepared(self : Sampler, row : PreparedRow) -> Result[Preview, SamplingError]

    Preview a prepared row without redoing softmax or ranking.

    Sampler::preview_weights

    fn Sampler::preview_weights(self : Sampler, weights : Array[Double]) -> Result[Preview, SamplingError]

    Sampler::reset

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

    Sampler::restore

    fn Sampler::restore(self : Sampler, checkpoint : Checkpoint) -> Result[Unit, SamplingError]

    Sampler::sample

    fn Sampler::sample(self : Sampler, logits : Array[Double], uniform : Double) -> Result[Step, SamplingError]

    One complete Mirostat step. State changes only after every calculation succeeds, so malformed logits cannot corrupt an ongoing generation.

    Sampler::sample_prepared

    fn Sampler::sample_prepared(self : Sampler, row : PreparedRow, uniform : Double) -> Result[Step, SamplingError]

    Draw using a prepared model row. Useful in simulations and repeated sampling from unchanged logits; real autoregressive models usually return a different row each step and can use sample directly.

    Sampler::sample_weights

    fn Sampler::sample_weights(self : Sampler, weights : Array[Double], uniform : Double) -> Result[Step, SamplingError]

    Sample from an already computed model distribution, supplied as weights. The weights are normalized internally and need not sum to one.

    Sampler::sample_with_rng

    fn Sampler::sample_with_rng(self : Sampler, logits : Array[Double], rng : ParkMiller) -> Result[Step, SamplingError]

    Convenience step using this package's reproducible generator. It consumes one draw even if the supplied logits are invalid; callers requiring exact rollback should validate or manage the RNG separately.

    Sampler::state

    fn Sampler::state(self : Sampler) -> SamplerState

    Sampler::steps

    fn Sampler::steps(self : Sampler) -> Int

    Sampler::to_repr

    Sampler::version

    fn Sampler::version(self : Sampler) -> Version

    SamplerState

    pub struct SamplerState {
    mu : Double
    steps : Int
    } derive(Eq,
    Debug
    )

    Portable feedback state for a host that stores Mirostat session data between calls. The configuration and version must be stored separately.

    SamplerState::equal

    SamplerState::mu

    fn SamplerState::mu(self : SamplerState) -> Double

    SamplerState::new

    fn SamplerState::new(mu : Double, steps : Int) -> Result[SamplerState, SamplingError]

    SamplerState::not_equal

    fn SamplerState::not_equal(x : SamplerState, y : SamplerState) -> Bool

    SamplerState::steps

    fn SamplerState::steps(self : SamplerState) -> Int

    SamplingError

    pub enum SamplingError {
    EmptyLogits
    AllMasked
    NonFiniteLogit(Int)
    InvalidUniform
    InvalidProbability(Int)
    InvalidParameter(String)
    NumericalFailure
    } derive(Eq,
    Debug
    )

    Errors are values so callers can decide whether to skip a malformed model row.

    SamplingError::equal

    SamplingError::invalid_parameter

    fn SamplingError::invalid_parameter(message : String) -> SamplingError

    SamplingError::message

    fn SamplingError::message(self : SamplingError) -> String

    SamplingError::not_equal

    fn SamplingError::not_equal(x : SamplingError, y : SamplingError) -> Bool

    Step

    pub struct Step {
    token : Int
    original_probability : Double
    observed_surprise : Double
    kept_tokens : Int
    mu_before : Double
    mu_after : Double
    } derive(Eq,
    Debug
    )

    Step::equal

    fn Step::equal(Step, Step) -> Bool

    Step::kept_tokens

    fn Step::kept_tokens(self : Step) -> Int

    Step::mu_after

    fn Step::mu_after(self : Step) -> Double

    Step::mu_before

    fn Step::mu_before(self : Step) -> Double

    Step::not_equal

    fn Step::not_equal(x : Step, y : Step) -> Bool

    Step::observed_surprise

    fn Step::observed_surprise(self : Step) -> Double

    Step::original_probability

    fn Step::original_probability(self : Step) -> Double

    Step::to_repr

    Step::token

    fn Step::token(self : Step) -> Int

    StopReason

    pub enum StopReason {
    Limit
    EndToken
    Observer
    } derive(Eq,
    Debug
    )

    StopReason::end_token

    fn StopReason::end_token() -> StopReason

    StopReason::equal

    fn StopReason::equal(StopReason, StopReason) -> Bool

    StopReason::limit

    fn StopReason::limit() -> StopReason

    StopReason::not_equal

    fn StopReason::not_equal(x : StopReason, y : StopReason) -> Bool

    StopReason::observer

    fn StopReason::observer() -> StopReason

    StreamGeneration

    pub struct StreamGeneration {
    tokens : Array[Int]
    reason : StopReason
    } derive(Eq,
    Debug
    )

    StreamGeneration::count

    fn StreamGeneration::count(self : StreamGeneration) -> Int

    StreamGeneration::equal

    StreamGeneration::not_equal

    fn StreamGeneration::not_equal(x : StreamGeneration, y : StreamGeneration) -> Bool

    StreamGeneration::reason

    StreamGeneration::tokens

    fn StreamGeneration::tokens(self : StreamGeneration) -> Array[Int]

    SurpriseWindow

    pub struct SurpriseWindow {
    capacity : Int
    values : Array[Double]
    next : Int
    count : Int
    total : Double
    } derive(
    Debug
    )

    Fixed-size online window for monitoring recent surprise without retaining an entire generation trace. Values are measured in bits.

    SurpriseWindow::capacity

    fn SurpriseWindow::capacity(self : SurpriseWindow) -> Int

    SurpriseWindow::clear

    fn SurpriseWindow::clear(self : SurpriseWindow) -> Unit

    SurpriseWindow::count

    fn SurpriseWindow::count(self : SurpriseWindow) -> Int

    SurpriseWindow::mean

    fn SurpriseWindow::mean(self : SurpriseWindow) -> Result[Double, SamplingError]

    SurpriseWindow::new

    fn SurpriseWindow::new(capacity : Int) -> Result[SurpriseWindow, SamplingError]

    SurpriseWindow::push

    fn SurpriseWindow::push(self : SurpriseWindow, surprise : Double) -> Result[Unit, SamplingError]

    SurpriseWindow::push_step

    fn SurpriseWindow::push_step(self : SurpriseWindow, step : Step) -> Result[Unit, SamplingError]

    TraceAccumulator

    pub struct TraceAccumulator {
    target : Double
    count : Int
    total_surprise : Double
    total_kept : Double
    absolute_error : Double
    final_mu : Double
    } derive(
    Debug
    )

    Online trace statistics for applications that do not want to retain every step. Push only successful Mirostat decisions into this accumulator.

    TraceAccumulator::count

    fn TraceAccumulator::count(self : TraceAccumulator) -> Int

    TraceAccumulator::new

    TraceAccumulator::push

    fn TraceAccumulator::push(self : TraceAccumulator, step : Step) -> Result[Unit, SamplingError]

    TraceAccumulator::summary

    TraceAccumulator::to_repr

    Newer MoonBit toolchains no longer implicitly promote these methods.

    TraceSpread

    pub struct TraceSpread {
    minimum : Double
    maximum : Double
    variance : Double
    median : Double
    p90 : Double
    minimum_kept : Int
    maximum_kept : Int
    } derive(Eq,
    Debug
    )

    Dispersion diagnostics for token-level observed surprise. Variance is the population variance in squared bits. Percentiles use nearest lower rank.

    TraceSpread::equal

    fn TraceSpread::equal(TraceSpread, TraceSpread) -> Bool

    TraceSpread::maximum

    fn TraceSpread::maximum(self : TraceSpread) -> Double

    TraceSpread::maximum_kept

    fn TraceSpread::maximum_kept(self : TraceSpread) -> Int

    TraceSpread::median

    fn TraceSpread::median(self : TraceSpread) -> Double

    TraceSpread::minimum

    fn TraceSpread::minimum(self : TraceSpread) -> Double

    TraceSpread::minimum_kept

    fn TraceSpread::minimum_kept(self : TraceSpread) -> Int

    TraceSpread::not_equal

    fn TraceSpread::not_equal(x : TraceSpread, y : TraceSpread) -> Bool

    TraceSpread::p90

    fn TraceSpread::p90(self : TraceSpread) -> Double

    TraceSpread::variance

    fn TraceSpread::variance(self : TraceSpread) -> Double

    TraceSummary

    pub struct TraceSummary {
    count : Int
    mean_surprise : Double
    mean_kept : Double
    mean_absolute_error : Double
    final_mu : Double
    } derive(Eq,
    Debug
    )

    Aggregate diagnostics for a completed sequence of Mirostat steps.

    TraceSummary::count

    fn TraceSummary::count(self : TraceSummary) -> Int

    TraceSummary::equal

    TraceSummary::final_mu

    fn TraceSummary::final_mu(self : TraceSummary) -> Double

    TraceSummary::mean_absolute_error

    fn TraceSummary::mean_absolute_error(self : TraceSummary) -> Double

    TraceSummary::mean_kept

    fn TraceSummary::mean_kept(self : TraceSummary) -> Double

    TraceSummary::mean_surprise

    fn TraceSummary::mean_surprise(self : TraceSummary) -> Double

    TraceSummary::not_equal

    fn TraceSummary::not_equal(x : TraceSummary, y : TraceSummary) -> Bool

    Version

    pub enum Version {
    V1
    V2
    } derive(Eq,
    Debug
    )

    Version::equal

    fn Version::equal(Version, Version) -> Bool

    Version::not_equal

    fn Version::not_equal(x : Version, y : Version) -> Bool

    Version::to_repr

    Version::v1

    fn Version::v1() -> Version

    Version::v2

    fn Version::v2() -> Version

    allow_token_ids

    fn allow_token_ids(logits : Array[Double], allowed_tokens : Array[Int]) -> Result[Array[Double], SamplingError]

    Keep only a sparse set of allowed token ids. A valid nonempty support must remain after applying the allowlist and any masks already in the logits.

    bias_logits

    fn bias_logits(logits : Array[Double], biases : Array[Double]) -> Result[Array[Double], SamplingError]

    Add a per-token logit bias. Negative infinity in the base row remains masked, and every bias must be finite. A bias of zero leaves the row alone.

    block_token_ids

    fn block_token_ids(logits : Array[Double], blocked : Array[Int]) -> Result[Array[Double], SamplingError]

    Mask a sparse list of forbidden token ids without building a full Boolean mask in the caller. Repeated ids have the same effect as one occurrence.

    draw_candidates

    fn draw_candidates(probabilities : Array[Double], candidates : Array[Int], uniform : Double) -> Result[Int, SamplingError]

    Draw from an arbitrary list of distinct candidate token indices. This accepts a partial vocabulary and retains the supplied candidate order.

    draw_prefix

    fn draw_prefix(probabilities : Array[Double], order : Array[Int], keep : Int, uniform : Double) -> Result[Int, SamplingError]

    Draw from a ranked prefix using one externally supplied uniform number. The caller controls randomness, making a run exactly replayable.

    entropy

    fn entropy(probabilities : Array[Double]) -> Result[Double, SamplingError]

    Shannon entropy of a model row, in bits. Zero-mass tail entries contribute nothing, as in the continuous limit of -p log p.

    epsilon_prefix_size

    fn epsilon_prefix_size(probabilities : Array[Double], order : Array[Int], epsilon : Double) -> Result[Int, SamplingError]

    Keep the ranked prefix whose tokens each have probability at least the absolute epsilon threshold. At least the best token survives.

    estimated_k

    fn estimated_k(mu : Double, exponent : Double, vocabulary : Int) -> Result[Int, SamplingError]

    The paper's adaptive top-k estimate. Near s=1 the limiting expression uses ln(vocabulary size), avoiding a 0/0 numerical failure.

    expected_prefix_surprise

    fn expected_prefix_surprise(probabilities : Array[Double], order : Array[Int], keep : Int) -> Result[Double, SamplingError]

    Expected observed surprise when drawing from a renormalized prefix. The surprise itself is measured against the full original model distribution.

    generate

    fn generate(sampler : Sampler, rng : ParkMiller, model : (Array[Int]) -> Result[Array[Double], SamplingError], limit : Int, end_token : Int?) -> Result[Generation, SamplingError]

    Drive an autoregressive model callback until an end token or step limit. The callback receives a copy of the generated prefix and must return the next logit row. The RNG is consumed only after a model row is available. Any error preserves successful prior sampler steps and RNG draws.

    generate_from_prompt

    fn generate_from_prompt(sampler : Sampler, rng : ParkMiller, model : (Array[Int]) -> Result[Array[Double], SamplingError], prompt : Array[Int], limit : Int, end_token : Int?) -> Result[Generation, SamplingError]

    Generate after a pre-existing prompt. The callback sees prompt followed by generated tokens; the returned token list contains only new tokens.

    generate_stream

    fn generate_stream(sampler : Sampler, rng : ParkMiller, model : (Array[Int]) -> Result[Array[Double], SamplingError], prompt : Array[Int], limit : Int, end_token : Int?, observer : (Step) -> Bool) -> Result[StreamGeneration, SamplingError]

    Stream each successful decision to an observer and avoid retaining Step metadata. Return false from the observer to stop after that token. End token detection takes priority when both conditions apply to one step.

    mask_logits

    fn mask_logits(logits : Array[Double], allowed : Array[Bool]) -> Result[Array[Double], SamplingError]

    Apply an allow-mask. This is useful when a grammar or host application restricts the next token vocabulary. At least one allowed finite logit is required. The input array is not mutated.

    min_p_prefix_size

    fn min_p_prefix_size(probabilities : Array[Double], order : Array[Int], min_p : Double) -> Result[Int, SamplingError]

    Keep every token whose original model probability is at least a fraction of the best token's probability. The best token is always kept.

    normalize_weights

    fn normalize_weights(weights : Array[Double]) -> Result[Array[Double], SamplingError]

    Normalize externally supplied non-negative weights. This is useful when a model backend already supplies probabilities rather than logits.

    penalize_counts

    fn penalize_counts(logits : Array[Double], history : Array[Int], presence : Double, frequency : Double) -> Result[Array[Double], SamplingError]

    Subtract an additive presence cost once per seen token and an additive frequency cost for every occurrence. Costs may be negative to reward repetition. This is a preprocessing option outside Mirostat feedback.

    penalize_repetition

    fn penalize_repetition(logits : Array[Double], history : Array[Int], penalty : Double) -> Result[Array[Double], SamplingError]

    Apply the usual sign-aware repetition penalty once for each token that appears in history. This is a caller-selected preprocessing step, not part of the Mirostat algorithm itself.

    perplexity

    fn perplexity(average_surprise : Double) -> Result[Double, SamplingError]

    Perplexity corresponding to average surprise measured in bits.

    prefix_entropy

    fn prefix_entropy(probabilities : Array[Double], order : Array[Int], keep : Int) -> Result[Double, SamplingError]

    Entropy of the renormalized candidate prefix, in bits. It differs from expected model surprise because the latter uses original probabilities.

    prefix_kl

    fn prefix_kl(probabilities : Array[Double], order : Array[Int], keep : Int) -> Result[Double, SamplingError]

    KL divergence in bits from the renormalized prefix distribution to the original distribution. It depends only on retained original probability mass and is zero when the entire support is kept.

    prefix_mass

    fn prefix_mass(probabilities : Array[Double], order : Array[Int], keep : Int) -> Result[Double, SamplingError]

    Original model mass covered by a ranked candidate prefix.

    prefix_surprise_variance

    fn prefix_surprise_variance(probabilities : Array[Double], order : Array[Int], keep : Int) -> Result[Double, SamplingError]

    Variance of token surprise under the renormalized candidate prefix. This exposes how widely token-level surprises may fluctuate around their mean.

    probabilities

    fn probabilities(logits : Array[Double]) -> Result[Array[Double], SamplingError]

    Stable softmax. Subtracting the maximum avoids overflow for large logits.

    rank

    fn rank(probabilities : Array[Double]) -> Result[Array[Int], SamplingError]

    Sort token ids by descending probability; ties favor the lower id.

    replay

    fn replay(sampler : Sampler, rows : Array[Array[Double]], draws : Array[Double]) -> Result[Array[Step], SamplingError]

    Replay precomputed model rows and RNG draws, returning every decision. A failed row leaves the sampler at the last successful step.

    replay_atomic

    fn replay_atomic(sampler : Sampler, rows : Array[Array[Double]], draws : Array[Double]) -> Result[Array[Step], SamplingError]

    Replay a batch as one transaction. If any row fails, feedback state is restored to its value before the batch.

    rolling_surprise

    fn rolling_surprise(steps : Array[Step], window : Int) -> Result[Array[Double], SamplingError]

    Average surprise over the most recent window tokens at every position. The initial entries use all observations available so far.

    running_surprise

    fn running_surprise(steps : Array[Step]) -> Array[Double]

    Cumulative average surprise after each generated token. This is the observed cross-entropy estimate used to assess long-run target control.

    sample_batch

    fn sample_batch(samplers : Array[Sampler], logits : Array[Array[Double]], uniforms : Array[Double]) -> Result[Array[Step], SamplingError]

    Sample one next token for each independent generation session. Input arrays must align by index. On an error, earlier sessions have already advanced.

    sample_batch_atomic

    fn sample_batch_atomic(samplers : Array[Sampler], logits : Array[Array[Double]], uniforms : Array[Double]) -> Result[Array[Step], SamplingError]

    Sample all sessions as a transaction. If any row fails, every sampler is restored to its previous feedback state. External RNG state is unchanged because uniforms are supplied as values.

    sample_epsilon

    fn sample_epsilon(logits : Array[Double], epsilon : Double, uniform : Double) -> Result[Int, SamplingError]

    sample_min_p

    fn sample_min_p(logits : Array[Double], min_p : Double, uniform : Double) -> Result[Int, SamplingError]

    Sample from a min-p filtered model row.

    sample_shared_prepared

    fn sample_shared_prepared(samplers : Array[Sampler], row : PreparedRow, uniforms : Array[Double]) -> Result[Array[Step], SamplingError]

    Draw one token for each independent session from a shared prepared model row. This is useful for Monte Carlo comparisons using one fixed logits row while each session maintains its own Mirostat feedback state.

    sample_temperature

    fn sample_temperature(logits : Array[Double], temperature : Double, uniform : Double) -> Result[Int, SamplingError]

    Independent temperature sampling without truncation.

    sample_top_k

    fn sample_top_k(logits : Array[Double], k : Int, uniform : Double) -> Result[Int, SamplingError]

    Stateless top-k sampling is useful as a controlled comparison for Mirostat.

    sample_top_p

    fn sample_top_p(logits : Array[Double], threshold : Double, uniform : Double) -> Result[Int, SamplingError]

    Stateless nucleus sampling on the original model distribution.

    sample_typical

    fn sample_typical(logits : Array[Double], threshold : Double, uniform : Double) -> Result[Int, SamplingError]

    summarize

    fn summarize(steps : Array[Step], target : Double) -> Result[TraceSummary, SamplingError]

    Summarize a trace against the requested surprise target.

    surprise

    fn surprise(probability : Double) -> Result[Double, SamplingError]

    Information content in bits under the untruncated model distribution.

    top_indices

    fn top_indices(probabilities : Array[Double], k : Int) -> Result[Array[Int], SamplingError]

    Return the k highest-probability token indices in descending order. A size-k heap avoids sorting the full vocabulary when k is small.

    top_p_prefix_size

    fn top_p_prefix_size(probabilities : Array[Double], order : Array[Int], threshold : Double) -> Result[Int, SamplingError]

    Return the smallest top-probability prefix with mass at least threshold.

    trace_spread

    fn trace_spread(trace : Array[Step]) -> Result[TraceSpread, SamplingError]

    typical_order

    fn typical_order(probabilities : Array[Double]) -> Result[Array[Int], SamplingError]

    Sort tokens by the distance between their surprise and the model entropy. Zero-probability tokens are placed last, with token id breaking ties.

    typical_prefix_size

    fn typical_prefix_size(probabilities : Array[Double], order : Array[Int], threshold : Double) -> Result[Int, SamplingError]

    Choose the smallest locally typical prefix with cumulative mass at least threshold. Unlike top-p, the candidate order need not follow probability.

    v2_prefix_size

    fn v2_prefix_size(probabilities : Array[Double], order : Array[Int], mu : Double) -> Result[Int, SamplingError]

    V2 uses the current surprise target as a cutoff on original token probabilities. It always keeps at least the most likely token.

    with_temperature

    fn with_temperature(logits : Array[Double], temperature : Double) -> Result[Array[Double], SamplingError]

    Scale logits by positive temperature before another sampling operation.

    zipf_exponent

    fn zipf_exponent(probabilities : Array[Double], order : Array[Int], m : Int) -> Result[Double, SamplingError]

    Estimate the Zipf exponent from adjacent probability ratios in the top m. This follows the paper's least-squares estimator and uses log base two.