ドキュメント

評価ハーネス

評価ハーネス

laya.evals は、ラベル付きデータセットを再現可能なスコアに、ベースラインを合否ゲートに変えます。これにより、品質の変化が手作業の確認ではなくレビュー可能な差分になります。

指標の計算とデータセットのパーサは純粋な Python と numpy だけで書かれており、torch をインポートしないので、重みなしで動きます。データセットをチェックポイントに対して実行するにはチェックポイントが必要で、通常の読み込み時間がかかります。

クイックスタート

# check the format without a model
laya-evals validate research/evals/fixture.jsonl

# score a labelled set on one checkpoint, with thresholds and a baseline
laya-evals run data.jsonl --model english --device cpu \
    --min-accuracy 0.8 --max-ece 0.05 --score-within 0.25 --slice language \
    --json report.json --markdown report.md

# compare a saved report to a baseline
laya-evals compare report.json --baseline baseline.json --tolerance choice_accuracy=0.02

laya eval ... はメイン CLI から同じことを行うもので、laya eval validate data.jsonl も動きます。

終了コードは、成功時が 0、しきい値かベースラインの許容差に失敗したときが 1、使い方のエラーが 2 です。run は全体の指標と要求されたスライスを標準出力に表示し、--json / --markdown を指定すると完全なレポートと Markdown の要約を書き出します。

ONNX エクスポートの評価

run --onnx PATH は、torch の Router ではなく ONNXAgent を通してエクスポート済みの ONNX モデルを採点します。そのため、ONNX デプロイ(scripts/export_onnx.py --quantize による INT8 コピーを含む)も、torch 経路と同じしきい値とベースラインでゲートされます。

python scripts/export_onnx.py --model convaiinnovations/laya --output laya.onnx --quantize
laya-evals run data.jsonl --onnx laya.int8.onnx --max-ece 0.05

--model はエクスポート元のチェックポイントを指定します。Hub の id かローカルパスで、english のような Router のショートネームではありません(この経路に Router がないため。既定は convaiinnovations/laya)。その config と tokenizer はそこから読み込まれます。エージェントは 1 つのチェックポイントを提供するので、データセットの行の model フィールドが別のチェックポイントを指す場合は、間違ったモデルが黙って答えるのではなく、明確なエラーで失敗します。--device は適用されません。--batch-size はエージェントにバッチ API があればそれを使い、なければ state ごとに 1 回の呼び出しにフォールバックします。--sort-by-length はそのバッチ API に転送されますが、state ごとのフォールバックには並べ替えるグループがありません。レポートの config ブロックには onnx のパスが記録されます。

research/evals/fixture.jsonl(ラベル付き 12 行、英語チェックポイント、CPU)での実測:

runner choice_acc noul_acc score_mae ece mean_conf p50 ms
torch Router 0.75 1.00 1.3418 0.1596 0.7304 116.8
--onnx fp32 0.75 1.00 1.3418 0.1596 0.7304 66.3
--onnx int8 0.75 1.00 1.3512 0.1658 0.7304 46.3

fp32 エクスポートは torch の数値を正確に再現し、量子化コピーは score_mae を 0.009、ece を 0.006 動かします。これは compare --tolerance がゲートすることを想定した種類のドリフトです。

データセット形式

1 行に 1 つの JSON オブジェクト(JSONL)です。空行と # で始まる行は無視されます。

フィールド 必須 意味
state はい 判断対象のテキスト、メール、チケット、JSON 文書
questions はい Laya の質問 dict。Router.predict が受け取るのとまったく同じ形
expected はい 質問 id をキーにした正解。choice はラベル、score は数値、noul は true/false
tags いいえ スライスに使う文字列
language いいえ スライスに使う言語コード
model いいえ この行のチェックポイントを強制します。--model がこれを上書きします。何も強制しない行は、Router が答えたチェックポイントでラベル付けされます

research/evals/dataset.template.jsonl にコメント付きの例があります。

指標

各指標は該当する答えごとに計算され、データセット全体で集計されます。

指標 対象 意味
choice_accuracy choice 選ばれたラベルが一致した割合
noul_accuracy noul 真偽値(確率 >= 0.5)が一致した割合
score_mae score 平均絶対誤差
score_within_<tol> score 絶対許容差の範囲内に入った割合
ece 信頼度を持つ任意の答え 期待キャリブレーション誤差。15 ビン、answer["answer_confidence"] で計算します。これは Laya がすべての答えの型で報告するキャリブレーション済み確率です
mean_confidence 信頼度を持つ任意の答え 報告された answer["answer_confidence"] の平均
latency_p50_ms、latency_p95_ms リクエストごと 各リクエストが待った実時間。参考情報 —— バッチ処理とタイミングを参照
cost_per_decision_p50_ms、cost_per_decision_p95_ms 意思決定ごと 呼び出しの実時間を運んだ行数で割ったもの。参考情報

許容差の指標を使うには、評価器のリストに ScoreWithin(0.25) を追加します。既定のセットは choice_accuracy、noul_accuracy、score_mae、mean_confidence と ece です。CLI からは同じことが 1 つのフラグで済みます。laya-evals run data.jsonl --score-within 0.25 は既定に加えて score_within_0.25 を報告します。フラグは繰り返せるので、--score-within 0.25 --score-within 0.5 は両方を報告します。

許容差の指標には数値ラベル付きの score の答えが必要なので、それがないデータセットでは値を持ちません。run は黙って 0 を出すのではなく計算できなかった指標を名指しし、その指標を名指す --min / --max ゲートは欠落として失敗します。実行に要求された許容差はレポートの config ブロックに記録されるので、レビュー済みのベースラインはどの列を期待するかを示します。

バッチ処理とタイミング

--batch-size N は、チェックポイントと質問スキーマを共有する最大 N 個の連続した行を 1 回の呼び出しで採点します。両方のタイミング指標は同じ計測に由来し、別の問いに答えます。バッチの各行はバッチが返るときに返るので、その latency は呼び出し全体で、cost_per_decision はその 1/N です。したがってバッチ処理は、変わらない意思決定の集合に対して latency_* を上げ、cost_per_decision_* を下げます。--max latency_p50_ms=... はリクエストが速く提供されたかを問うのであり、実行が安かったかを問うのではありません。--batch-size がなければ両者は一致します。

compare は許容差が名指ししない限り *_ms 指標を無視するので、タイミングのノイズでベースラインが落ちることはありません。ハーネスが実際に行ったこと —— 要求されたバッチサイズ、解決された runner の形、何行が 1 回の呼び出しを共有したか、最大のチャンク —— はレポートの config.timing に記録されます。フラグだけでは何かがバッチ化されたか分からないからです。これらのカウンタは発行された呼び出しを記録し、返った呼び出しは記録しません。on_error=skip では、呼び出しが例外を投げたチャンクも rows_grouped と max_chunk に数えられ、config.errored のエントリと並びます。2 つの *_ms 指標は返った呼び出しだけを数えるので、失敗した呼び出しが計測していないレイテンシを寄与することはありません。

バッチ内の行のグループ化

--sort-by-length は似た長さの行を同じフォワードパスにグループ化するので、各パスはその中の最長行ではなく短い最大長にパディングします。これは呼び出しの形であって答えではありません。結果は同じ順序で返り、同じように採点されます。だからこそ research/ は 10,000 チケットで、意思決定を変えずに 2.15 倍を報告できます。

並べ替えるには複数のパスが必要なので、--batch-size N が実行のグループ化する行数より小さいときにだけ効きます。config.timing は 2 つの主張を切り分けて保持します。sort_by_length はコマンドラインが言ったことで、sort_by_length_sent は runner に届いたことです。--batch-size のない実行は起こりえないことを要求し、sent: false でそう述べます。predict_batch がこのつまみより前の runner は、長い実行の途中で TypeError を投げるのではなく、並べ替えなしで採点されます。

スライス

compare と run は全体の数値を報告し、--slice language|model|qid|tag に対してはスライス値ごとに同じ指標を報告します。だから 1 つの言語や 1 つの質問での回帰が、集計を読まなくても見えます。model スライスは各行に答えたチェックポイントを保持します。Router 自身のリクエストごとの選択か、ルーティングしない runner ならその runner の model です。

実行の同一性

run は測定したものをレポートの config ブロックに記録するので、レビュー担当者が読む成果物はそれ自体でレビュー可能です。

キー 意味
schema レポートの形。laya-evals-report/1。読み取れないものを消費者が拒否できるようにしています
dataset 入力されたとおりのパス —— ハッシュではなく名前
dataset_sha256 解析されたデータセットのバイト列の sha256
questions_sha256 質問スキーマの指紋。データセット全体にわたる各質問の id、type、instructions、criteria
laya_version 数値を計算した laya
thresholds この実行が適用したゲート。min、max、baseline_tolerance
revisions 答えた各チェックポイントがどのコミットから読み込まれたか(下記を参照)

dataset はパスであり、パスは同一性ではありません。データセットはその場で編集され、移動され、同じ名前で再取得されうるし、CI のキャッシュは 2 つの実行に同じファイル名と異なるバイト列を渡しえます。questions_sha256 は行数ではなく何を尋ねたかを対象にするので、変わらない質問セットに state を追加しても指紋はそのままです。dataset_sha256 はそれでも動きます。行の追加はデータの変更であり、質問の変更ではないからです。

instructions も対象に含みます。指示テキストがプロンプトだからです。build_sequence は "<type> question: <instructions>" をトークン化されたヘッドにレンダリングし、Agent はそれのない質問を拒否し(「モデルが答えるべきテキストを追加してください」)、Laya 自身の質問の同一性もすでにそれを数えています。Router._question_schema とこのハーネスのバッチグループ化はどちらも questions dict 全体をキーにし、tests/test_router_batch.py は instructions を言い換えるだけで行が別のバッチグループに移ることを固定しています。では言い換えた指示は依然としてベースラインと等しく比較されるのでしょうか。いいえ —— そしてそこが要点です。「返金が正当かどうかを判断せよ」と「保守的に、明示的な返金要求のみを承認せよ」は別の質問であり、指標ゲートはその差がたまたま名指しした許容差より大きく数値を動かしたときにしか気づけません。choice の選択肢に名前を付けるのも同じ論法です。criteria は意思決定空間であり、research/eval/metamorphic.py のメタモルフィック検査が存在するのは、ラベルの改名が答えを反転させるからです。

指示テキストについて正規化されるものは、エンジン自身が適用する 1 つのステップを除いて何もありません。文字列でない instructions は json.dumps(ins, ensure_ascii=False) としてハッシュされ、Agent._to_internal と一致します。したがって空白も言い回しも数えられ、人間が校正とみなす言い換えも新しい実験として扱われます。これが正直な既定です —— 代替案は、実行とそのベースラインの間に類似度ヒューリスティックを置くことで、常用される評価システムにそんなものはありません。

時間を含むものは何も記録されないので、固定した runner に対してレポートは依然としてバイト単位で再現可能です。

REPORT_SCHEMA、questions_fingerprint(dataset)、file_fingerprint(path) は公開されているので、laya.evals.evaluate を直接駆動する呼び出し元も CLI 実行と同じ同一性を得ます。

ベースラインと CI ゲート

  • データセット、ベースラインレポート(レビュー済みの --json 出力)、許容差を一緒に、コミットして保つと、変更がレビュー可能な差分になります。--tolerance METRIC=VALUE はその指標に許される最大の絶対ドリフトです。
  • laya-evals run ... --baseline baseline.json --tolerance ... はドリフトで非ゼロ終了するので、そのまま CI に組み込めます。laya.evals.EvalReport.compare と assert_regression はテスト向けに同じロジックを公開します。

指標ゲートは「数値が動いたか」に答えます。「同じ数値だったか」には答えられません。compare は overall だけを読み、overall しか読まないからです。そのため、あるデータセットに対して記録されたベースラインは、別のデータセットで採点された候補を、算術が同一のまま通してしまいます。EvalReport.comparable_to がそれを埋めます。schema、dataset_sha256、questions_sha256 を比較し、run --baseline と compare はすべての差分を表示したうえで、キーと両方の値を名指しして非ゼロ終了で失敗します。

FAIL: baseline is not comparable: dataset_sha256 (dataset bytes): baseline is <sha>, this run is <sha>

どちらかの側に欠けているキーは競合ではなく不明なので、同一性が存在する前に書かれたすべてのレポートはこれまでとまったく同じように比較され続けます。これには下のスケジュール済みゲートのベースラインも含まれ、それは research/eval/ から来ており config.schema をまったく持ちません。

この仕組みを使う CI の面は 2 つあります。

  • .github/workflows/ci.yml の重み不要のジョブは tests/test_evals.py と tests/test_evals_api.py を実行するので、指標の計算・データセットの解析・CLI が、チェックポイントをダウンロードせずにすべての PR でカバーされます。
  • .github/workflows/evals.yml は週次で、リリース前とオンデマンドに実行されます。MASSIVE 英語スイートで英語チェックポイントを評価し、research/evals/thresholds.json の許容差で research/results/eval_english_51_languages.json と比較します。レポートを成果物としてアップロードし、PR はブロックしません。

ハーネスは固定したチェックポイントリビジョンに対して決定的なので、レポートは再現可能です。run はデータセット・モデル・デバイスに加えて、実行のタイミングの事実をレポートの config ブロックに記録し、revisions には答えた各チェックポイントが実際に読み込まれたコミットを記録します。--revision <SHA> は実行が読み込むすべてのチェックポイントについてそのコミットを固定し、--revision english=<SHA> は 1 つのチェックポイントを固定します(繰り返し可)。自動ルーティングの実行が望むのはこの形です。3 つのチェックポイントは 3 つのリポジトリで、1 つのコミットがそのすべてに存在することはできないからです。固定しなければ、実行はチェックポイントの既定ブランチを取り、レポートはどのコミットが答えたかを依然として示すので、ベースラインのドリフトを重みのせいにもコードのせいにもできます。laya/revisions.py はオプトインしたい呼び出し元向けに、レビュー済みのコミット SHA を PINNED_REVISIONS で公開しています。--onnx では、素の --revision <SHA> だけが config と tokenizer のダウンロードに適用されます。

実際のラベル付きセットを追加する

research/evals/ に JSONL を、その隣にレビュー済みのベースラインを置き、ワークフロー(または research/evals/check_regression.py)を両方に向けてください。形式は fixture と同じで、ハーネスのどこも MASSIVE を知りません。