tune API

Luna-Flow/mare_mark/tune 提供自动调优的策略部分:候选空间、预算和留出集、稳健分数、带实际阈值的选择、Pareto 前沿,以及带种子的候选顺序。它不构建也不运行候选;由你的应用测量它们(通常使用 runner),再把数值交给这些函数。参见 tune 设计。

源码:src/tune/tune.mbt。

import {
  "Luna-Flow/mare_mark/tune",
}

候选

CandidateSpace

CandidateSpace 描述调优器可以尝试的配置。

pub struct CandidateSpace[Candidate] {
  enumerate : () -> Array[Candidate]
  candidate_id : (Candidate) -> String
  valid : (Candidate) -> Bool
  neighbors : (Candidate) -> Array[Candidate]
}
pub fn[Candidate] CandidateSpace::new(() -> Array[Candidate], (Candidate) -> String, (Candidate) -> Bool, (Candidate) -> Array[Candidate]) -> Self[Candidate]

enumerate 以确定的顺序列出空间,candidate_id 给出唯一且稳定的 id,valid 在测量之前拒绝违反约束的配置,neighbors 为你自己编写的局部搜索列出邻近配置(本包中没有函数使用它)。

BuiltCandidate

BuiltCandidate 把候选 id 与为它构建的可运行实现配对。

pub struct BuiltCandidate[Prepared, Output, Context] {
  candidate_id : String
  implementation : @runner.Implementation[Prepared, Output, Context]
  kernel_id : String
}
pub fn[Prepared, Output, Context] BuiltCandidate::new(String, @runner.Implementation[Prepared, Output, Context], String) -> Self[Prepared, Output, Context]

BuildEvent

BuildEvent 记录候选是否被接受,以及未被接受的原因。

pub struct BuildEvent {
  candidate_id : String
  kernel_id : String
  accepted : Bool
  reason : String
}

对被接受的候选,reason 为 "";对被 valid 拒绝的候选为 "constraint";对分数不可用的候选为 "measurement"。

预算与目标

TuningBudget

TuningBudget 限定一次调优运行。

pub struct TuningBudget[Scale] {
  max_candidates : Int
  max_measurements : Int
  max_elapsed_us : Double?
  exploration_samples : Int
  confirmation_samples : Int
  finalists : Int
  holdout : HoldoutPlan[Scale]
}
pub fn[Scale] TuningBudget::new(Int, Int, Double?, Int, Int, Int, HoldoutPlan[Scale]) -> Self[Scale]

这些字段描述一个两阶段搜索:用 exploration_samples 探索每个候选,用 confirmation_samples 确认最好的 finalists 个候选,并在 holdout 上检查胜者。该记录是供你的调优循环使用的数据;本包中没有函数强制执行它。

HoldoutPlan

HoldoutPlan 指明为检验胜者能否泛化而保留的数据。

pub(all) enum HoldoutPlan[Scale] {
  MeasurementHoldout(Int)
  DatasetHoldout(Array[Int])
  ShapeHoldout(Array[Scale])
  WorkloadHoldout(String)
  Combined(Array[HoldoutPlan[Scale]])
}
构造器保留的数据
MeasurementHoldout(n)最后 n 次测量
DatasetHoldout(ids)这些数据集
ShapeHoldout(scales)这些规模或形状
WorkloadHoldout(name)一个命名的工作负载
Combined(plans)上述所有已列出的内容

TuningObjective

TuningObjective 说明“最好”的含义。

pub struct TuningObjective {
  practical_delta_pct : Double
  max_workspace_bytes : UInt64?
  minimize_secondary : Bool
}
pub fn TuningObjective::new(Double, UInt64?, Bool) -> Self

与最快者相差在 practical_delta_pct 以内的候选视为持平;持平时按次要指标(最小化或最大化)决出。

分数

CandidateScore

CandidateScore 是一个候选的测量结果。

pub struct CandidateScore {
  candidate_id : String
  primary : Double
  secondary : Double
  valid : Bool
}
pub fn CandidateScore::new(String, Double, Double, Bool) -> Self

primary 是要最小化的代价(时间),secondary 是决胜指标(内存、代码大小)。当 valid 为真且两个值都是有限且非负的数时,分数可用;下面的函数会忽略其他分数。

score_samples

score_samples 把计时样本转化为分数:可用样本的中位数。

pub fn score_samples(String, Array[Double], Double) -> CandidateScore

NaN、无穷大或负数的样本会被丢弃。若没有剩余样本,或次要值不是有限非负数,则分数的 valid = false 且 primary = 0.0。

test "median score" {
  let score = @tune.score_samples("64x64", [9.0, 1.0, 5.0, -1.0, 3.0], 4096.0)
  inspect(score.primary, content="4")
  inspect(score.valid, content="true")
}

select_best

select_best 在可用分数中选出胜者。

pub fn select_best(Array[CandidateScore], Double, Bool) -> CandidateScore?

参数:分数、以百分比表示的实际阈值 tt,以及是否最小化次要指标。设 pmin⁡p_{\min} 为最小的主要值,决赛候选是满足 100 (p−pmin⁡)/pmin⁡≤t100\,(p - p_{\min})/p_{\min} \le t 的分数(当 pmin⁡=0p_{\min} = 0 时,为 p=0p = 0 的分数)。在它们之中,返回次要值最好的那个,再按最小 id 决出。NaN、无穷大或负的阈值按 0 处理。没有可用分数时返回 None。结果不取决于输入的顺序。

test "fast enough, then small" {
  let scores = [
    @tune.CandidateScore::new("fastest", 100.0, 900.0, true),
    @tune.CandidateScore::new("lean", 103.0, 100.0, true),
    @tune.CandidateScore::new("leaner-but-slow", 110.0, 10.0, true),
  ]
  inspect(@tune.select_best(scores, 5.0, true).unwrap().candidate_id, content="lean")
  inspect(@tune.select_best(scores, 0.0, true).unwrap().candidate_id, content="fastest")
}

pareto_frontier

pareto_frontier 保留不被任何其他可用分数支配的可用分数。

pub fn pareto_frontier(Array[CandidateScore]) -> Array[CandidateScore]

当 o.p≤c.po.p \le c.p、o.s≤c.so.s \le c.s 且两者之一为严格不等时,分数 oo 支配 cc(两个指标都取最小化)。结果先按主要值、再按次要值、最后按 id 排序。代价:O(n2)O(n^2)。

test "Pareto front" {
  let front = @tune.pareto_frontier([
    @tune.CandidateScore::new("a", 1.0, 9.0, true),
    @tune.CandidateScore::new("b", 2.0, 4.0, true),
    @tune.CandidateScore::new("c", 3.0, 5.0, true),
    @tune.CandidateScore::new("d", 4.0, 1.0, true),
  ])
  debug_inspect(front.map(s => s.candidate_id), content="[\"a\", \"b\", \"d\"]")
}

搜索辅助函数

exhaustive_scores

exhaustive_scores 为候选空间的一个前缀打分,并指出最快的可用候选。

pub fn[Candidate] exhaustive_scores(CandidateSpace[Candidate], Int, (Candidate) -> CandidateScore) -> TuningResult

它取 enumerate() 的前 min⁡(budget,∣space∣)\min(\text{budget}, \lvert\text{space}\rvert) 个候选。每个有效候选由回调打分;无效候选则不打分。策略字符串为主要值最小的可用候选的 "global:<id>"(持平时取第一个),若没有可用候选则为 "global:"。

test "exhaustive search" {
  let space = @tune.CandidateSpace::new(
    () => [8, 16, 32, 64],
    n => "block-" + n.to_string(),
    n => n <= 32,
    _ => [],
  )
  let cost = [8.0, 5.0, 6.0, 1.0]
  let result = @tune.exhaustive_scores(space, 10, n => {
    let index = if n == 8 { 0 } else if n == 16 { 1 } else if n == 32 { 2 } else { 3 }
    @tune.CandidateScore::new("block-" + n.to_string(), cost[index], 0.0, true)
  })
  inspect(result.policy, content="global:block-16")
  inspect(result.build_events[3].reason, content="constraint")
}

TuningResult

TuningResult 是 exhaustive_scores 的结果。

pub struct TuningResult {
  scores : Array[CandidateScore]
  build_events : Array[BuildEvent]
  policy : String
}

scores 保存有效候选(无论是否可用)的分数,build_events 为每个被检查的候选保存一个事件。

seeded_order

seeded_order 按候选 id 的带种子哈希对候选进行置换。

pub fn[Candidate] seeded_order(Array[Candidate], UInt64, (Candidate) -> String) -> Array[Candidate]

每个 id 用以种子为密钥的 FNV-1a 进行哈希;候选先按哈希、再按 id 排序。结果取决于 id 集合和种子,而不取决于输入顺序。可取它的一个前缀作为可复现的随机子集。

test "seeded order is input-order independent" {
  let a = @tune.seeded_order(["x", "y", "z"], 9UL, s => s)
  let b = @tune.seeded_order(["z", "x", "y"], 9UL, s => s)
  inspect(a == b, content="true")
}

confirmation_count

confirmation_count 根据测得的不确定度调整确认样本的数量。

pub fn confirmation_count(Int, Double, Int) -> Int

对于 u≤0.05u \le 0.05、0.05<u≤0.20.05 < u \le 0.2 和 u>0.2u > 0.2,confirmation_count(base, u, budget) 分别为 base 的 1、2、3 倍,并截断到 [0,budget][0, \text{budget}]。u 是你自选的相对不确定度,例如 IQR 除以中位数。

test "more samples when noisy" {
  inspect(@tune.confirmation_count(10, 0.01, 100), content="10")
  inspect(@tune.confirmation_count(10, 0.1, 100), content="20")
  inspect(@tune.confirmation_count(10, 0.5, 25), content="25")
}