Harnais d’évaluation
laya.evals transforme un jeu de données étiqueté en score reproductible, et une référence en porte
réussite/échec, pour qu’un changement de qualité soit un diff relisible plutôt qu’une vérification à la
main.
Le calcul des métriques et le parseur de jeu de données sont du Python pur plus numpy et n’importent jamais torch, donc ils tournent sans poids. Exécuter un jeu de données contre un checkpoint nécessite le checkpoint et prend son temps de chargement normal.
Démarrage rapide
# 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 ... est la même chose via la CLI principale, donc laya eval validate data.jsonl
fonctionne aussi.
Codes de sortie : 0 en cas de succès, 1 quand un seuil ou une tolérance de référence échoue, 2
en cas d’erreur d’usage. run imprime les métriques globales et les tranches demandées sur stdout, et
écrit le rapport complet et un résumé Markdown quand --json / --markdown sont donnés.
Attribuer les erreurs de présélection
Pour un ensemble choice étiqueté à cardinalité élevée, laya.evals_shortlist.evaluate_shortlist
utilise le chemin predict_shortlist existant et le harnais d’évaluation habituel. Il répond à deux
questions distinctes : la récupération a-t-elle conservé le libellé de référence, et Laya l’a-t-il
choisi quand il était présent ? C’est une API Python opt-in pour les libellés choice ; les rapports
ordinaires de laya-evals run sont inchangés.
import laya
from laya.evals import Dataset
from laya.evals_shortlist import evaluate_shortlist
from laya.shortlist import embed_fn_from_agent
agent = laya.load()
dataset_path = "intents.jsonl"
dataset = Dataset.from_jsonl(dataset_path)
report = evaluate_shortlist(
agent, dataset, embed_fn_from_agent(agent), k=20,
checkpoint_id="my-checkpoint@revision", embedder_id="my-encoder@revision",
dataset_path=dataset_path,
)
print(report.overall)
print(report.cases[0]["shortlist_status"])
Utilise la même fonction d’embedding et le même checkpoint que le déploiement mesuré. Les deux
identifiants sont fournis par l’appelant et doivent nommer des révisions immuables ; le rapport ne
peut pas déduire les poids derrière un callable arbitraire. dataset_path enregistre le SHA256 du
fichier à côté de l’empreinte de questions existante. Chaque cas conserve les libellés réels de la
présélection et l’un de correct, retrieval_miss ou decision_miss. shortlist_recall_at_k est la
fraction de libellés de référence conservés. shortlist_accuracy_on_recalled est le nombre de
décisions correctes divisé par le nombre de cas conservés ; il est omis quand aucun ne l’a été. Le
choice_accuracy existant reste l’exactitude de bout en bout sur tous les cas, y compris les échecs
de récupération. Les métriques de présélection apparaissent dans les mêmes tranches langue, modèle,
question et tag. La latence de requête inclut l’embedding et l’appel de décision ; le rapport
n’isole pas les temps par étape. Avec k >= n, la question d’origine passe telle quelle et le rappel
de récupération vaut 1 sans appeler l’embedder.
Cela ne reproduit pas les résultats BANKING77 de l’issue #102 : ces nombres dépendent de son jeu de données, de son checkpoint et de son bi-encoder. Cette API rend le même type de diagnostic reproductible sur le propre jeu étiqueté d’un appelant.
Évaluer un export ONNX
run --onnx PATH évalue un modèle ONNX exporté via ONNXAgent au lieu du Router torch, donc un
déploiement ONNX (y compris une copie INT8 de scripts/export_onnx.py --quantize) passe par les mêmes
seuils et références que le chemin 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 nomme le checkpoint d’où vient l’export — un id Hub ou un chemin local, pas un nom court de
Router comme english, puisqu’il n’y a pas de Router sur ce chemin (défaut convaiinnovations/laya).
Sa config et son tokenizer sont chargés depuis là. L’agent sert un seul checkpoint, donc une ligne de
jeu de données dont le champ model en nomme un autre échoue avec une erreur claire plutôt que d’être
silencieusement répondue par le mauvais modèle ; --device ne s’applique pas. --batch-size utilise
l’API de lot de l’agent quand il en a une et retombe sur un appel par état sinon ; --sort-by-length
est transmis à cette API de lot ; le fallback par état n’a aucun groupe à réordonner. Passe
--calibration PATH pour charger une carte de calibration ajustée sur ONNXAgent, afin que les
portes de calibration comme --max-ece s’évaluent contre des probabilités calibrées. Le bloc config
du rapport enregistre le chemin onnx et le chemin calibration (quand il est défini).
Mesuré sur research/evals/fixture.jsonl (12 lignes étiquetées, checkpoint anglais, CPU) :
| runner | choice_acc | noul_acc | score_mae | ece | mean_conf | p50 ms |
|---|---|---|---|---|---|---|
| Router torch | 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 |
L’export fp32 reproduit exactement les nombres torch, et la copie quantifiée déplace score_mae de
0.009 et ece de 0.006 — le genre de dérive que compare --tolerance est censé gater.
Format du jeu de données
Un objet JSON par ligne (JSONL). Les lignes vides et les lignes commençant par # sont ignorées.
| champ | requis | signification |
|---|---|---|
state |
oui | texte, email, ticket ou document JSON sur lequel décider |
questions |
oui | un dict de questions Laya, exactement comme Router.predict l’accepte |
expected |
oui | vérité terrain indexée par id de question : un libellé pour choice, un nombre pour score, true/false pour noul |
tags |
non | chaînes par lesquelles découper |
language |
non | un code par lequel découper |
model |
non | force un checkpoint pour cette ligne ; --model le surcharge. Une ligne qui ne force rien est étiquetée avec le checkpoint avec lequel le Router a répondu |
research/evals/dataset.template.jsonl a un exemple commenté.
Métriques
Chaque métrique est calculée par réponse là où elle s’applique et agrégée sur le jeu de données :
| métrique | s’applique à | signification |
|---|---|---|
choice_accuracy |
choice |
fraction dont le libellé choisi correspond |
noul_accuracy |
noul |
fraction dont le booléen (probabilité >= 0.5) correspond |
score_mae |
score |
erreur absolue moyenne |
score_within_<tol> |
score |
fraction dans une tolérance absolue |
ece |
toute réponse avec une confiance | erreur de calibration attendue, 15 bacs, calculée sur answer["answer_confidence"], la probabilité calibrée que Laya rapporte sur chaque type de réponse |
brier |
toute réponse avec une confiance et un libellé connu | score de Brier de la confiance comme P(correct), mean((confidence - correct)**2) ; plus bas vaut mieux |
aurc |
toute réponse avec une confiance et un libellé connu | aire sous la courbe risque–couverture : une valeur de risque par niveau de confiance distinct, chacune pondérée par les réponses que ce niveau couvre ; plus bas vaut mieux, et récompense une confiance qui classe le correct du faux plutôt que d’être seulement calibrée |
selective_accuracy@50, selective_accuracy@80 |
toute réponse avec une confiance et un libellé connu | exactitude sur les réponses qu’un seuil de confiance au point de couverture 50 % / 80 % accepte – ce que rapporte le fait de s’abstenir sur la queue la moins confiante. Un seuil ne peut pas scinder un groupe de confiances égales, donc cela peut couvrir plus que la fraction nommée ; voir coupures de couverture |
mean_confidence |
toute réponse avec une confiance | moyenne de answer["answer_confidence"] rapportée |
latency_p50_ms, latency_p95_ms |
par requête | temps mural que chaque requête a attendu, informatif – voir traitement par lots |
cost_per_decision_p50_ms, cost_per_decision_p95_ms |
par décision | le temps mural d’un appel divisé par les lignes qu’il portait, informatif |
Coupures de couverture et égalités
Les deux métriques de couverture découpent sur un seuil de confiance, et un seuil accepte chaque
réponse à sa propre confiance. Une coupure ne scinde donc jamais un groupe de réponses qui partagent
la même : quand coverage * n tombe à l’intérieur d’un tel groupe, chaque membre du groupe est
accepté. Le nombre de réponses derrière le chiffre est donc le bord supérieur du groupe plutôt que la
fraction nommée – selective_accuracy@50 sur une tranche dont les confiances sont toutes égales est
l’exactitude propre de cette tranche, pas sa meilleure moitié. Le compte qu’imprime la porte (n=
dans le message d’échec d’une règle) est la taille de la tranche, pas la taille acceptée, donc un
groupe très large n’est pas visible depuis le message seul.
Les égalités sont le cas normal plutôt qu’un cas limite : une température ajustée peut laisser un bac
rapporter une masse ponctuelle, ce que laya.common.answer_confidence enregistre du choice:11+
livré, et un vrai checkpoint a produit un groupe de six lignes à exactement 1.0 sur douze réponses.
Découper à un index de ligne à la place faisait dépendre les deux métriques de l’ordre d’arrivée du
jeu de données – les mêmes lignes, mélangées, déplaçaient selective_accuracy@50 entre 0.000 et
1.000.
aurc intègre une valeur de risque par niveau distinct, pondérée par les réponses que ce niveau
couvre, donc elle reste une aire sous la courbe risque–couverture plutôt qu’une moyenne de points de
tailles inégales.
Deux conséquences à anticiper :
- Un nombre peut bouger dans un sens ou dans l’autre, et plus qu’un simple réordonnancement ne le
pourrait. Là où un groupe chevauche la coupure, la lecture par seuil diffère de toute lecture par
index de ligne des mêmes données : mesuré sur 400,001 jeux de données en forme d’égalités, jusqu’à
0.500 pour
selective_accuracy@50et 0.351 pouraurc. Sur la forme à douze réponses ci-dessus – six correctes, toutes à une confiance de 1.0 –aurcbouge de 0.327 (0.173 à 0.500). Une porte qui réussissait peut échouer, et une qui échouait peut réussir ; le verdict précédent dépendait de l’ordre des lignes, y compris pour une limite absolueminoumax, que rien ne refuse parce qu’elle lit une seule exécution. - Régénère les références commitées.
config.coverage_metric_definitionenregistre quelle définition a produit un rapport. Comparer une métrique de couverture entre deux définitions est refusé sur les deux portes qui soustraient une référence –--baseline --tolerance(EvalReport.compare) et une règle relative (max_drop/max_increase) sous--gate-policy– et dès que l’un ou l’autre côté est périmé, pas seulement la référence : un candidat produit par unlayaplus ancien porte un artefact d’ordre des lignes qui peut lire mieux que la vérité, donc le gater contre une référence correctement régénérée laisserait passer une régression que le rapport correctement noté fait échouer. Sans ce refus, une référence périmée masque une vraie régression : une tranche enregistrée à 0.033 sous l’ancienne définition lit 0.517 sous celle-ci, donc un candidat qui a réellement chuté de 0.217 franchirait unmax_dropde 0.05.eceetbrierne découpent pas et restent comparables.
Sans égalités dans les données, il y a un niveau par réponse, et les deux métriques sont exactement ce qu’elles ont toujours été – identiques bit à bit, pas seulement proches.
Ajoute ScoreWithin(0.25) à la liste d’évaluateurs pour une métrique de tolérance ; l’ensemble par
défaut est choice_accuracy, noul_accuracy, score_mae, mean_confidence, plus ece. Depuis la CLI,
la même chose est un drapeau : laya-evals run data.jsonl --score-within 0.25 rapporte
score_within_0.25 à côté des défauts, et le drapeau se répète, donc
--score-within 0.25 --score-within 0.5 rapporte les deux.
Une métrique de tolérance a besoin d’une réponse score avec un libellé numérique, donc sur un jeu de
données qui n’en a pas elle n’a pas de valeur : run nomme la métrique qu’il n’a pas pu calculer au lieu
de publier un zéro silencieux, et une porte --min / --max nommant cette métrique échoue comme
manquante. Les tolérances demandées à une exécution sont enregistrées dans le bloc config du rapport,
donc une référence revue dit quelles colonnes elle attend.
Traitement par lots et timing
--batch-size N évalue jusqu’à N lignes consécutives qui partagent un checkpoint et un schéma de
questions en un seul appel. Les deux métriques de timing viennent des mêmes mesures et répondent à des
questions différentes : chaque ligne d’un lot revient quand le lot revient, donc sa latency est tout
l’appel, tandis que son cost_per_decision en est 1/N. Le traitement par lots augmente donc
latency_* et abaisse cost_per_decision_* sur un ensemble de décisions inchangé, et
--max latency_p50_ms=... demande si les requêtes ont été servies vite, pas si l’exécution a été bon
marché. Sans --batch-size, les deux s’accordent.
compare ignore toute métrique *_ms sauf si une tolérance la nomme, donc celles-ci ne font jamais
échouer une référence sur du bruit de timing. Ce que le harnais a réellement fait – la taille de lot
demandée, la forme de runner vers laquelle il a résolu, combien de lignes partageaient un appel, et le
plus grand morceau – est enregistré dans config.timing du rapport, parce que le drapeau seul ne dit
pas si quoi que ce soit a été mis en lot. Ces compteurs enregistrent les appels émis, pas les appels qui
sont revenus : avec laya-evals run --on-error skip, un morceau dont l’appel a levé compte quand même dans
rows_grouped et max_chunk, à côté de ses entrées dans config.errored. Le défaut est
--on-error fail, qui relève l’erreur au lieu de publier un rapport dont les métriques ne couvrent que
les appels revenus. Les deux métriques *_ms ne comptent que les appels qui sont revenus, donc un appel
échoué ne contribue jamais une latence qu’il n’a pas mesurée.
Grouper les lignes dans un lot
--sort-by-length groupe les lignes de taille similaire dans la même passe avant, pour que chaque passe
se remplisse jusqu’à un maximum plus court au lieu de la ligne la plus longue du lot. C’est la forme des
appels, pas leurs réponses : les résultats reviennent dans le même ordre et obtiennent le même score,
c’est pourquoi research/ peut rapporter 2.15x sur 10,000 tickets sans qu’aucune décision ne change.
Il faut plus d’une passe pour réordonner, donc cela ne prend effet qu’avec un --batch-size N inférieur
au nombre de lignes que l’exécution groupe. config.timing tient les deux affirmations séparées :
sort_by_length est ce que la ligne de commande a dit, sort_by_length_sent est ce qui a atteint le
runner. Une exécution sans --batch-size demande quelque chose qui ne peut pas arriver, et le dit avec
sent: false ; un runner dont le predict_batch est antérieur au réglage est évalué non trié plutôt que
de lever TypeError au milieu d’une longue exécution.
La porte d’abstention à un seuil
--min-confidence T transmet le seuil d’abstention opt-in du cœur (#361) à chaque appel que fait
l’exécution, donc Router et ONNXAgent marquent les réponses dont l’answer_confidence tombe sous
T avec low_confidence: True avant que le harnais ne les voie. Contrairement au regroupement, cela
change les réponses qui sont notées : la même exécution à T=0 et T=0.7 est une expérience
différente, et un balayage precision@coverage est une série de celles-ci, pas une seule référence
qui dérive.
La plage acceptée est celle de laya.confidence.check_min_confidence du cœur – [0.0, 1.0], finie,
pas un bool – plutôt qu’une copie ici, donc une valeur que la porte elle-même rejetterait échoue
comme erreur d’usage (sortie 2) avant qu’aucun checkpoint ne se charge. 0.0 est une demande légale :
c’est le bras de contrôle d’un balayage precision@coverage, et une vérification qui l’écarterait
masquerait le plancher propre du balayage.
Un runner dont le predict ou (pour une exécution par lots) dont le predict_batch est antérieur à
la porte est refusé avec un EvalError nommé, pas noté sans le seuil. Laisser tomber
silencieusement un contrôle de notation est le genre de mensonge que ce harnais existe pour empêcher :
le rapport publierait un chiffre precision@coverage pour une politique qui n’a jamais tourné.
config.timing enregistre à la fois la demande et le fait : min_confidence est le seuil qui a été
demandé, min_confidence_sent dit si un appel de cette exécution l’a réellement porté.
Tranches
compare et run rapportent les nombres globaux et, pour --slice language|model|qid|tag, les mêmes
métriques par valeur de tranche, donc une régression dans une langue ou une question est visible sans
lire l’agrégat. La tranche model contient le checkpoint qui a répondu à chaque ligne : le choix du
Router lui-même par requête, ou le model du runner pour un runner qui ne route pas.
Portes de tranche opt-in
La porte de référence globale peut réussir pendant qu’une tranche de langue ou de question plus petite
régresse. Pour faire d’une tranche revue une exigence de CI, enregistre une politique JSON comme
gates.json :
{
"version": 1,
"rules": [
{"slice": {"language": "zh"}, "metric": "choice_accuracy",
"min_count": 50, "max_drop": 0.05},
{"slice": {"qid": "intent"}, "metric": "ece",
"min_count": 50, "max": 0.10}
]
}
laya-evals run data.jsonl --baseline baseline.json --tolerance choice_accuracy=0.02 \
--gate-policy gates.json --json report.json
laya-evals compare report.json --baseline baseline.json \
--tolerance choice_accuracy=0.02 --gate-policy gates.json
Chaque règle sélectionne exactement une valeur language, model, qid ou tag et nomme la
métrique exactement comme elle apparaît dans le rapport de tranche. Elle a un min_count positif et
exactement une limite : min ou max vérifie la valeur du candidat ; max_drop autorise au plus
cette baisse par rapport à la référence ; max_increase autorise au plus cette hausse. Les deux
dernières exigent --baseline. Le compte est le nombre de réponses notées pour cette métrique dans la
tranche sélectionnée, dans les deux rapports pour une règle relative. Pour ece, c’est le nombre
de réponses avec une confiance finie et une valeur booléenne correct. Une tranche ou une métrique
manquante, trop peu de réponses notées, ou des cas ignorés/en erreur font échouer la porte opt-in.
Les règles relatives exigent aussi que les deux rapports portent des identités d’exécution
correspondantes, pour qu’une preuve manquante ne puisse pas apparaître comme une réussite. Une
régression mesurée rapporte la tranche, la métrique, les comptes, les valeurs et la limite. Une
syntaxe de politique invalide sort en 2 avant qu’un checkpoint ne se charge ; un échec de qualité sort
en 1. La politique est enregistrée dans config.gate_policy d’un rapport run --json.
compare --gate-policy applique la politique fournie sur cette ligne de commande aux mesures
enregistrées. Si elle diffère de la politique enregistrée dans le rapport, compare le dit ; une
revérification explicite sous une nouvelle politique ne change pas la politique sous laquelle
l’exécution d’origine a été faite.
La comparaison globale habituelle s’applique toujours, y compris sa tolérance et son comportement de
référence héritée. Sans --gate-policy, les rapports de tranche et la comparaison se comportent comme
avant.
Identité d’exécution
run enregistre ce qu’il a mesuré dans le bloc config du rapport, donc l’artefact que lit un
relecteur est relisible en lui-même :
| clé | signification |
|---|---|
schema |
la forme du rapport, laya-evals-report/1, pour qu’un consommateur puisse refuser un qu’il ne peut pas lire |
dataset |
le chemin tel que saisi – un nom, pas un hash |
dataset_sha256 |
le sha256 des octets du jeu de données qui ont été parsés |
questions_sha256 |
une empreinte du schéma de questions : l’id, le type, instructions et criteria de chaque question, sur tout le jeu de données |
laya_version |
le laya qui a calculé les nombres |
coverage_metric_definition |
quelle définition de aurc / selective_accuracy@* a produit ce rapport (voir coupures de couverture). Une règle de porte relative sur l’une ou l’autre refuse une référence enregistrée sous une définition différente, plutôt que de soustraire des nombres qui ne veulent pas dire la même chose |
gate_policy |
la politique optionnelle de portes de tranche appliquée par run --gate-policy |
thresholds |
la porte appliquée par cette exécution : min, max et baseline_tolerance |
revisions |
le commit depuis lequel chaque checkpoint qui a répondu a été chargé (voir ci-dessous) |
dataset est un chemin, et un chemin n’est pas une identité : un jeu de données peut être modifié sur
place, déplacé, ou retéléchargé sous le même nom, et un cache CI peut donner à deux exécutions le même
nom de fichier et des octets différents. questions_sha256 couvre ce qui a été demandé plutôt que
combien de lignes il y avait, donc ajouter des états à un ensemble de questions inchangé laisse
l’empreinte tranquille – dataset_sha256 bouge quand même, et ajouter une ligne est un changement des
données, pas de la question.
Il couvre instructions aussi, parce que le texte d’instruction est le prompt. build_sequence rend
"<type> question: <instructions>" dans la tête tokenisée, Agent refuse une question sans instruction
(« ajoute le texte auquel le modèle doit répondre »), et l’identité de question de Laya la compte déjà :
Router._question_schema et le groupement par lots de ce harnais indexent tous deux sur le dict de
questions entier, et tests/test_router_batch.py épingle qu’une reformulation des instructions seule
déplace une ligne dans son propre groupe de lot. Donc une instruction reformulée se compare-t-elle quand
même égale à une référence ? Non – et c’est le but. « Juge si un remboursement est justifié » et
« Sois prudent et n’approuve que les demandes de remboursement explicites » posent des questions
différentes, et la porte de métrique ne peut le remarquer que quand la différence déplace un nombre
au-delà de la tolérance que tu as nommée. Nommer une option de choice relève du même argument :
criteria est l’espace de décision, et les vérifications métamorphiques de
research/eval/metamorphic.py existent parce que renommer un libellé inverse des réponses.
Rien dans le texte d’instruction n’est normalisé, sauf la seule étape que le moteur applique lui-même :
un instructions non-chaîne est haché comme json.dumps(ins, ensure_ascii=False), comme
Agent._to_internal. Donc les espaces et la formulation comptent tous les deux, et une reformulation
qu’un humain considère comme une correction est traitée comme une nouvelle expérience. C’est le défaut
honnête – l’alternative est une heuristique de similarité entre une exécution et sa référence, et
aucun système d’évaluation courant n’en a.
Rien qui porte un horodatage n’est enregistré, donc un rapport reste reproductible octet pour octet pour un runner fixe.
REPORT_SCHEMA, questions_fingerprint(dataset) et file_fingerprint(path) sont publics, donc un
appelant qui pilote directement laya.evals.evaluate obtient la même identité qu’une exécution CLI.
Référence et porte CI
- Garde le jeu de données, un rapport de référence (sortie
--jsonque tu as relue) et les tolérances ensemble, commités, pour qu’un changement soit un diff relisible.--tolerance METRIC=VALUEest la dérive absolue maximale autorisée pour cette métrique. laya-evals run ... --baseline baseline.json --tolerance ...sort en non-zéro en cas de dérive, donc il s’intègre tel quel dans la CI.laya.evals.EvalReport.compareetassert_regressionexposent la même logique pour les tests.
La porte de métrique répond à « les nombres ont-ils bougé ». Elle ne peut pas répondre à « étaient-ce les
mêmes nombres », parce que compare lit overall et seulement overall – donc une référence
enregistrée contre un jeu de données validerait un candidat évalué sur un autre, avec une arithmétique
identique. EvalReport.comparable_to referme cela : il compare schema, dataset_sha256 et
questions_sha256, et run --baseline et compare impriment quand même chaque delta, puis échouent
avec un code de sortie non nul en nommant la clé et les deux valeurs :
FAIL: baseline is not comparable: dataset_sha256 (dataset bytes): baseline is <sha>, this run is <sha>
Une clé absente d’un côté est inconnue, pas un conflit, donc chaque rapport écrit avant que l’identité
n’existe continue de se comparer exactement comme avant. Cela inclut la référence de la porte planifiée
ci-dessous, qui vient de research/eval/ et n’a aucun config.schema.
Deux surfaces CI utilisent ceci :
- un job sans poids dans
.github/workflows/ci.ymlexécutetests/test_evals.pyettests/test_evals_api.py, donc le calcul des métriques, le parsing de jeu de données et la CLI sont couverts à chaque PR sans télécharger de checkpoint ; .github/workflows/evals.ymltourne chaque semaine, avant une release et à la demande : il évalue le checkpoint anglais sur la suite MASSIVE anglaise et compare àresearch/results/eval_english_51_languages.jsonavec les tolérances deresearch/evals/thresholds.json. Il téléverse le rapport comme artefact et ne bloque pas une PR.
Le harnais est déterministe pour une révision de checkpoint fixe, donc un rapport est reproductible.
run enregistre le jeu de données, le modèle et l’appareil, plus les faits de timing
de l’exécution, dans le bloc config du rapport, et revisions : le commit depuis lequel chaque
checkpoint qui a répondu a été réellement chargé. --revision <SHA> épingle ce commit pour chaque
checkpoint que l’exécution charge, et --revision english=<SHA> épingle un checkpoint (répétable) — ce
qui est la forme que veut une exécution en auto-routage, puisque les trois checkpoints sont trois dépôts
et qu’un commit ne peut pas exister dans tous. Laissé non épinglé, l’exécution prend la branche par
défaut du checkpoint et le rapport dit quand même quel commit a répondu, donc une dérive de référence
peut être attribuée aux poids ou au code. laya/revisions.py publie des SHA de commit revus dans
PINNED_REVISIONS pour les appelants qui veulent s’y inscrire. Avec --onnx, seul un --revision <SHA>
nu s’applique, au téléchargement de la config et du tokenizer.
Ajouter le vrai jeu étiqueté
Dépose un JSONL dans research/evals/ et une référence revue à côté, puis pointe un workflow (ou
research/evals/check_regression.py) vers les deux. Le format est le même que celui du fixture ; rien
dans le harnais ne connaît MASSIVE.