tune のチュートリアル

このチュートリアルでは、小さなチューニングループを実行します。候補空間を定義し、計時サンプルから候補をスコア付けし、実用的な閾値で勝者を選び、パレートフロント上のトレードオフを確認し、大きな空間を再現可能なランダム部分集合に絞り込みます。例が決定的になるよう計時値は数値で与えていますが、実際には runner の観測から得られます。

クイックスタート

moon add Luna-Flow/mare_mark@0.3.0
import {
  "Luna-Flow/mare_mark/tune",
}
test "pick a block size" {
  let samples = [
    ("block-16", [5.1, 5.0, 5.3]),
    ("block-32", [4.2, 4.4, 4.1]),
    ("block-64", [4.3, 4.2, 4.3]),
  ]
  let scores = samples.map(entry => @tune.score_samples(entry.0, entry.1, 0.0))
  inspect(@tune.select_best(scores, 0.0, true).unwrap().candidate_id, content="block-32")
}

日常的な作業

同率の 2 つの候補のうち安価な方を選ぶ

block-64 は block-32 から 3 % 以内にあり、必要なワークスペースは 4 分の 1 です。閾値 5 % で、ワークスペースを副次的な指標とすると、こちらが勝ちます:

test "ties go to the smaller workspace" {
  let scores = [
    @tune.score_samples("block-32", [4.2, 4.4, 4.1], 65536.0),
    @tune.score_samples("block-64", [4.3, 4.2, 4.3], 16384.0),
  ]
  inspect(@tune.select_best(scores, 5.0, true).unwrap().candidate_id, content="block-64")
}

トレードオフを示す

test "time versus memory" {
  let front = @tune.pareto_frontier([
    @tune.score_samples("tiny", [9.0], 1024.0),
    @tune.score_samples("small", [5.0], 4096.0),
    @tune.score_samples("medium", [5.5], 8192.0),
    @tune.score_samples("large", [4.0], 65536.0),
  ])
  debug_inspect(front.map(s => s.candidate_id), content="[\"large\", \"small\", \"tiny\"]")
}

medium は small より遅く、かつ大きいため、フロント上にはありません。

制約付きで空間を探索する

test "exhaustive search with constraints" {
  let space = @tune.CandidateSpace::new(
    () => [(16, 16), (32, 32), (64, 64), (128, 128)],
    tile => tile.0.to_string() + "x" + tile.1.to_string(),
    tile => tile.0 * tile.1 * 8 <= 65536,
    _ => [],
  )
  let measured : Map[String, Array[Double]] = Map([
    ("16x16", [6.0, 6.1]), ("32x32", [4.1, 4.0]), ("64x64", [3.9, 4.0]),
  ])
  let result = @tune.exhaustive_scores(space, 4, tile => {
    let id = tile.0.to_string() + "x" + tile.1.to_string()
    @tune.score_samples(id, measured.get(id).unwrap_or([]), 0.0)
  })
  inspect(result.policy, content="global:64x64")
  inspect(result.build_events[3].reason, content="constraint")
}

128×128 のタイルは 128 KiB を必要とし、計測される前に却下されます。

大きな空間から再現可能にサンプリングする

test "a seeded subset" {
  let all = Array::makei(100, i => "candidate-" + i.to_string())
  let first_ten = @tune.seeded_order(all, 2026UL, id => id)[:10].to_owned()
  let again = @tune.seeded_order(all.rev(), 2026UL, id => id)[:10].to_owned()
  inspect(first_ten == again, content="true")
}

同じシードからは、空間がどの順序で列挙されていても同じ 10 個の候補が得られます。シードを結果とともに記録してください。

さらに先へ

  • 最終候補を確認する。 上位のいくつかを @tune.confirmation_count(base, relative_iqr, budget) 個の新しいサンプルで再計測し、それに基づいて改めて選択します。最初の選択が楽観的になる理由は tune の設計で説明しています。
  • 形状をホールドアウトする。 いくつかの形状で選び、その後 ShapeHoldout で勝者を検査します。
  • ランナーで計測する。 各候補を @runner.Implementation に包み、ブロックを共有するよう 1 つのケースで実行し、候補ごとの確認的な中央値を score_samples に渡します。
  • 実例。 tune_gemm はこれらすべてをブロック化された行列積に適用しています。

よくある落とし穴

  • 探索データだけで選択する。 勝者の呪いにより、実際より良く見えてしまいます。
  • ノイズの多いデータでゼロの閾値を使う。 同率がノイズによって決まってしまいます。
  • 一意でない ID。 選択と順序付けは ID が一意であることを前提とします。

次のステップ