Harness de avaliação
O laya.evals transforma um conjunto de dados etiquetado num score repetível, e uma linha de base
num gate de passa/falha, para que uma alteração de qualidade seja um diff revisível em vez de uma
verificação manual.
A matemática das métricas e o parser do conjunto de dados são Python puro mais numpy e nunca importam a torch, por isso correm sem pesos. Correr um conjunto de dados contra um checkpoint precisa do checkpoint e leva o seu tempo de carregamento normal.
Início rápido
# 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 ... é a mesma coisa através da CLI principal, por isso laya eval validate data.jsonl
também funciona.
Códigos de saída: 0 em caso de sucesso, 1 quando um limiar ou uma tolerância de linha de base
falha, 2 num erro de utilização. O run imprime as métricas globais e quaisquer fatias pedidas
para o stdout, e escreve o relatório completo e um sumário Markdown quando são dados --json /
--markdown.
Atribuir erros de shortlist
Para um conjunto de escolhas etiquetado de alta cardinalidade, laya.evals_shortlist.evaluate_shortlist
usa o caminho predict_shortlist existente e o harness de avaliação regular. Responde a duas
perguntas separadas: a recuperação manteve a etiqueta de ouro, e o Laya escolheu-a quando ela estava
presente? Esta é uma API Python opt-in para etiquetas de choice; os relatórios comuns de
laya-evals run não mudam.
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"])
Use a mesma função de embedding e o mesmo checkpoint da implementação que está a ser medida. Os dois
identificadores são fornecidos pelo autor da chamada e devem nomear revisões imutáveis; o relatório
não consegue inferir os pesos por trás de um callable arbitrário. dataset_path regista o SHA256 do
ficheiro ao lado da impressão digital de perguntas existente. Cada caso mantém as etiquetas de
shortlist reais e um de entre correct, retrieval_miss ou decision_miss.
shortlist_recall_at_k é a fração de etiquetas de ouro retidas. shortlist_accuracy_on_recalled são
as decisões corretas divididas pelos casos retidos; é omitido quando nenhum foi retido. O
choice_accuracy existente continua a ser a exatidão de ponta a ponta sobre todos os casos,
incluindo falhas de recuperação. As métricas de shortlist aparecem nas mesmas fatias de idioma,
modelo, pergunta e tag. A latência do pedido inclui o embedding e a chamada de decisão; o relatório
não isola os tempos de cada etapa. Com k >= n, a pergunta original passa diretamente e a recuperação
tem recall 1 sem chamar o embedder.
Isto não reproduz os resultados do BANKING77 na issue #102: esses números dependem do conjunto de dados, do checkpoint e do bi-encoder dela. Esta API torna o mesmo tipo de diagnóstico repetível num conjunto etiquetado do próprio autor da chamada.
Avaliar uma exportação ONNX
run --onnx PATH pontua um modelo ONNX exportado através do ONNXAgent em vez do Router torch, por
isso uma implementação ONNX (incluindo uma cópia INT8 de scripts/export_onnx.py --quantize) é
sujeita aos mesmos limiares e linhas de base que o caminho 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 nomeia o checkpoint de onde veio a exportação — um id do Hub ou caminho local, e não um
nome curto do Router como english, já que não há Router neste caminho (predefinição
convaiinnovations/laya). A sua config e o tokenizer são carregados a partir daí. O agente serve
um só checkpoint, por isso uma linha do conjunto de dados cujo campo model nomeie outro falha com
um erro claro em vez de ser respondida silenciosamente pelo modelo errado; --device não se
aplica. --batch-size usa a API de lotes do agente quando ele tem uma e cai para uma chamada por
estado caso contrário; --sort-by-length é encaminhado para essa API de lotes; o fallback por estado
não tem grupo para reordenar. Passe --calibration PATH para carregar um mapa de calibração ajustado
no ONNXAgent, para que os gates de calibração como --max-ece avaliem contra probabilidades
calibradas. O bloco config do relatório regista o caminho onnx e o caminho calibration (quando
definido).
Medido em research/evals/fixture.jsonl (12 linhas etiquetadas, checkpoint inglês, 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 |
A exportação fp32 reproduz exatamente os números da torch, e a cópia quantizada move score_mae em
0.009 e ece em 0.006 — o tipo de desvio que o compare --tolerance se destina a barrar.
Formato do conjunto de dados
Um objeto JSON por linha (JSONL). As linhas em branco e as linhas que começam com # são
ignoradas.
| campo | obrigatório | significado |
|---|---|---|
state |
sim | texto, email, ticket ou documento JSON sobre o qual decidir |
questions |
sim | um dict de pergunta do Laya, exatamente como o Router.predict aceita |
expected |
sim | verdade de referência indexada por id de pergunta: uma etiqueta para choice, um número para score, true/false para noul |
tags |
não | strings pelas quais fatiar |
language |
não | um código pelo qual fatiar |
model |
não | força um checkpoint para esta linha; --model substitui-o. Uma linha que não força nada é etiquetada com o checkpoint com que o Router respondeu |
O research/evals/dataset.template.jsonl tem um exemplo comentado.
Métricas
Cada métrica é calculada por resposta sempre que se aplica e agregada sobre o conjunto de dados:
| métrica | aplica-se a | significado |
|---|---|---|
choice_accuracy |
choice |
fração cuja etiqueta escolhida coincide |
noul_accuracy |
noul |
fração cujo booleano (probabilidade >= 0.5) coincide |
score_mae |
score |
erro absoluto médio |
score_within_<tol> |
score |
fração dentro de uma tolerância absoluta |
ece |
qualquer resposta com confiança | erro de calibração esperado, 15 bins, calculado sobre answer["answer_confidence"], a probabilidade calibrada que o Laya reporta em todos os tipos de resposta |
brier |
qualquer resposta com confiança e etiqueta conhecida | Brier score da confiança como P(correct), mean((confidence - correct)**2); menor é melhor |
aurc |
qualquer resposta com confiança e etiqueta conhecida | área sob a curva risco–cobertura: um valor de risco por nível de confiança distinto, cada um ponderado pelas respostas que esse nível abrange; menor é melhor, e recompensa uma confiança que ordena certo e errado, em vez de estar apenas calibrada |
selective_accuracy@50, selective_accuracy@80 |
qualquer resposta com confiança e etiqueta conhecida | exatidão sobre as respostas que um limiar de confiança no ponto de cobertura de 50% / 80% aceita – o que abster-se da cauda menos confiante compra. Um limiar não consegue dividir um grupo de confianças iguais, por isso isto pode cobrir mais do que a fração nomeada; vê cortes de cobertura |
mean_confidence |
qualquer resposta com confiança | média do answer["answer_confidence"] reportado |
latency_p50_ms, latency_p95_ms |
por pedido | tempo de relógio que cada pedido esperou, informativo – vê processamento em lote |
cost_per_decision_p50_ms, cost_per_decision_p95_ms |
por decisão | o tempo de relógio de uma chamada dividido pelas linhas que transportou, informativo |
Cortes de cobertura e empates
Ambas as métricas de cobertura cortam num limiar de confiança, e um limiar aceita toda a resposta
na sua própria confiança. Por isso um corte nunca divide um grupo de respostas que partilham uma:
quando coverage * n cai dentro desse grupo, todos os membros do grupo são aceites. O número de
respostas por trás do valor é, portanto, o limite superior do grupo, e não a fração nomeada –
selective_accuracy@50 sobre uma fatia cujas confianças são todas iguais é a exatidão da própria
fatia, não a melhor metade dela. A contagem que o gate imprime (n= na mensagem de falha de uma
regra) é o tamanho da fatia, não o tamanho aceite, por isso um grupo muito largo não é visível só
pela mensagem.
Empates são o caso normal, e não um canto: uma temperatura ajustada pode deixar um bucket a reportar
uma massa pontual, que laya.common.answer_confidence regista do choice:11+ distribuído, e um
checkpoint real produziu um grupo de seis linhas exatamente em 1.0 de doze respostas. Cortar num
índice de linha em vez disso fez ambas as métricas dependerem da ordem em que o conjunto de dados
chegou – as mesmas linhas, baralhadas, moveram selective_accuracy@50 entre 0.000 e 1.000.
aurc integra um valor de risco por nível distinto, ponderado pelas respostas que esse nível
abrange, por isso continua a ser uma área sob a curva risco–cobertura, e não uma média de pontos de
tamanhos desiguais.
Duas consequências a planear:
- Um número pode mover-se em qualquer direção, por mais do que uma reordenação poderia. Onde um
grupo atravessa o corte, a leitura por limiar difere de toda a leitura por índice de linha dos
mesmos dados: medido em 400,001 conjuntos de dados em forma de empate, até 0.500 para
selective_accuracy@50e 0.351 paraaurc. Na forma de doze respostas acima – seis corretas, todas com confiança 1.0 –aurcmove-se 0.327 (0.173 a 0.500). Um gate que estava a passar pode falhar, e um que estava a falhar pode passar; o veredicto anterior dependia da ordem das linhas, incluindo para um limiteminoumaxabsoluto, que nada recusa porque lê uma única execução. - Regenere as linhas de base cometidas.
config.coverage_metric_definitionregista qual definição produziu um relatório. Comparar uma métrica de cobertura entre duas definições é recusado em ambos os gates que subtraem uma linha de base –--baseline --tolerance(EvalReport.compare) e uma regra relativa (max_drop/max_increase) sob--gate-policy– e em qualquer dos lados estar desatualizado, não apenas a linha de base: um candidato produzido por umlayamais antigo transporta um artefacto de ordem de linha que pode ler melhor do que a verdade, por isso barrá-lo contra uma linha de base regenerada corretamente deixaria passar uma regressão que o relatório pontuado corretamente reprova. Sem essa recusa, uma linha de base desatualizada esconde uma regressão real: uma fatia registada em 0.033 sob a definição antiga lê 0.517 sob esta, por isso um candidato que caiu genuinamente 0.217 passaria ummax_dropde 0.05.eceebriernão cortam e continuam comparáveis.
Sem empates nos dados há um nível por resposta, e ambas as métricas são exatamente o que sempre foram – idênticas bit a bit, não apenas próximas.
Acrescenta ScoreWithin(0.25) à lista de avaliadores para uma métrica de tolerância; o conjunto
predefinido é choice_accuracy, noul_accuracy, score_mae, mean_confidence, mais ece. A
partir da CLI a mesma coisa é um só flag: laya-evals run data.jsonl --score-within 0.25 reporta
score_within_0.25 a par das predefinições, e o flag repete-se, por isso --score-within 0.25 --score-within 0.5 reporta ambos.
Uma métrica de tolerância precisa de uma resposta score com uma etiqueta numérica, por isso num
conjunto de dados sem uma não tem valor: o run nomeia a métrica que não conseguiu calcular em vez
de publicar um zero silencioso, e um gate --min / --max que nomeie essa métrica falha como em
falta. As tolerâncias que foram pedidas a uma execução são registadas no bloco config do
relatório, por isso uma linha de base revista diz que colunas espera.
Processamento em lote e temporização
--batch-size N pontua até N linhas consecutivas que partilham um checkpoint e um esquema de
perguntas numa só chamada. Ambas as métricas de temporização vêm das mesmas medições e respondem a
perguntas diferentes: cada linha de um lote regressa quando o lote regressa, por isso a sua
latency é toda a chamada, enquanto o seu cost_per_decision é 1/N dela. O processamento em
lote, portanto, aumenta latency_* e baixa cost_per_decision_* num conjunto de decisões
inalterado, e --max latency_p50_ms=... pergunta se os pedidos foram servidos depressa, não se a
execução foi barata. Sem --batch-size as duas coincidem.
O compare ignora qualquer métrica *_ms a menos que uma tolerância a nomeie, por isso estas
nunca falham uma linha de base por ruído de temporização. O que o harness fez de facto – o tamanho
de lote pedido, a forma de runner em que resolveu, quantas linhas partilharam uma chamada, e o maior
bloco – é registado no config.timing do relatório, porque o flag sozinho não diz se algo foi
processado em lote. Esses contadores registam as chamadas emitidas, não as chamadas que regressaram:
com laya-evals run --on-error skip, um bloco cuja chamada levantou exceção ainda conta em
rows_grouped e max_chunk, a par das suas entradas em config.errored. A predefinição é
--on-error fail, que volta a levantar a exceção em vez de publicar um relatório cujas métricas
cobrem apenas as chamadas que regressaram. As duas métricas *_ms contam apenas as chamadas que
regressaram, por isso uma chamada falhada nunca contribui com uma latência que não mediu.
Agrupar as linhas dentro de um lote
--sort-by-length agrupa linhas de tamanho semelhante na mesma passagem direta, por isso cada
passagem preenche até um máximo mais curto em vez de até à linha mais longa nela. É a forma das
chamadas, não as suas respostas: os resultados voltam na mesma ordem e pontuam de forma idêntica, e
é por isso que research/ pode reportar 2.15x sobre 10,000 tickets sem nenhuma decisão mudar.
Tem de haver mais do que uma passagem para reordenar, por isso só tem efeito com um --batch-size N
abaixo do número de linhas que a execução agrupa. O config.timing mantém as duas afirmações
separadas: sort_by_length é o que a linha de comandos disse, sort_by_length_sent é o que chegou
ao runner. Uma execução sem --batch-size pede algo que não pode acontecer, e di-lo com
sent: false; um runner cujo predict_batch é anterior a este botão é pontuado sem ordenação em
vez de levantar TypeError a meio de uma execução longa.
O gate de abstenção num limiar
--min-confidence T encaminha o limiar de abstenção opt-in do core (#361) para cada chamada que a
execução faz, por isso o Router e o ONNXAgent marcam as respostas cujo answer_confidence fica
abaixo de T com low_confidence: True antes de o harness as ver. Ao contrário do agrupamento, isto
muda as respostas que pontuam: a mesma execução com T=0 e T=0.7 é uma experiência diferente, e
uma varredura de precision@coverage é uma série destas, não uma única linha de base à deriva.
O intervalo aceite é o laya.confidence.check_min_confidence do core – [0.0, 1.0], finito, não
um bool – em vez de uma cópia aqui, por isso um valor que o próprio gate recusaria falha como erro
de utilização (saída 2) antes de qualquer checkpoint carregar. 0.0 é um pedido legítimo: é o braço
de controlo de uma varredura de precision@coverage, e uma verificação que o descartasse esconderia
o próprio piso da varredura.
Um runner cujo predict ou (para uma execução em lote) cujo predict_batch é anterior ao gate é
recusado com um EvalError nomeado, não pontuado sem o limiar. Descartar silenciosamente um
controlo de pontuação é a classe de mentira que este harness existe para prevenir: o relatório
publicaria um valor de precision@coverage para uma política que nunca correu. config.timing
regista tanto o pedido como o facto: min_confidence é o limiar que foi solicitado,
min_confidence_sent diz se alguma chamada desta execução o transportou de facto.
Fatias
O compare e o run reportam números globais e, para --slice language|model|qid|tag, as mesmas
métricas por valor de fatia, por isso uma regressão num idioma ou numa pergunta é visível sem ler o
agregado. A fatia model contém o checkpoint que respondeu a cada linha: a escolha do próprio
Router por pedido, ou o model do runner para um runner que não encaminha.
Gates de fatia opt-in
O gate de linha de base global pode passar enquanto uma fatia mais pequena de idioma ou pergunta
regride. Para tornar uma fatia revista um requisito de CI, guarde uma política JSON como
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
Cada regra seleciona exatamente um valor de language, model, qid ou tag e nomeia a métrica
exatamente como aparece no relatório de fatia. Tem um min_count positivo e exatamente um limite:
min ou max verifica o valor do candidato; max_drop permite no máximo essa diminuição em relação
à linha de base; max_increase permite no máximo esse aumento. Os dois últimos exigem --baseline.
A contagem é o número de respostas pontuadas para essa métrica na fatia selecionada, em ambos os
relatórios para uma regra relativa. Para ece, é o número de respostas com confiança finita e valor
correct booleano. Uma fatia ou métrica em falta, poucas respostas pontuadas, ou casos
ignorados/com erro reprovam o gate opt-in. As regras relativas também exigem que ambos os relatórios
transportem identidades de execução correspondentes, para que evidência em falta não possa aparecer
como uma aprovação. Uma regressão medida reporta a fatia, a métrica, as contagens, os valores e o
limite. Sintaxe de política inválida sai com 2 antes de um checkpoint carregar; uma falha de qualidade
sai com 1. A política é registada em config.gate_policy de um relatório de run --json. O
compare --gate-policy aplica a política fornecida nessa linha de comandos às medições guardadas. Se
for diferente da política registada no relatório, o compare di-lo; uma reverificação explícita sob
uma nova política não altera a política sob a qual a execução original foi feita.
A comparação global regular ainda se aplica, incluindo a sua tolerância e o comportamento de linha de
base legada. Sem --gate-policy, o relato e a comparação de fatias comportam-se como antes.
Identidade da execução
O run registra o que mediu no bloco config do relatório, por isso o artefacto que um revisor lê
é revisível por si só:
| chave | significado |
|---|---|
schema |
a forma do relatório, laya-evals-report/1, para que um consumidor possa recusar um que não consiga ler |
dataset |
o caminho tal como foi escrito – um nome, não um hash |
dataset_sha256 |
o sha256 dos bytes do conjunto de dados que foram analisados |
questions_sha256 |
uma impressão digital do esquema de perguntas: o id, tipo, instructions e criteria de cada pergunta, sobre todo o conjunto de dados |
laya_version |
o laya que calculou os números |
coverage_metric_definition |
qual definição de aurc / selective_accuracy@* produziu este relatório (vê cortes de cobertura). Uma regra de gate relativa sobre qualquer um deles recusa uma linha de base registada sob uma definição diferente, em vez de subtrair números que não significam a mesma coisa |
thresholds |
o gate que esta execução aplicou: min, max e baseline_tolerance |
gate_policy |
a política opcional de gate de fatia aplicada por run --gate-policy |
revisions |
o commit a partir do qual foi carregado cada checkpoint que respondeu (vê abaixo) |
dataset é um caminho, e um caminho não é uma identidade: um conjunto de dados pode ser editado no
local, movido, ou reobtido com o mesmo nome, e uma cache de CI pode entregar a duas execuções o
mesmo nome de ficheiro e bytes diferentes. questions_sha256 cobre o que foi pedido em vez de
quantas linhas havia, por isso acrescentar estados a um conjunto de perguntas inalterado deixa a
impressão digital intacta – dataset_sha256 continua a mudar, e acrescentar uma linha é uma
alteração aos dados, não à pergunta.
Cobre também instructions, porque o texto da instrução é o prompt. O build_sequence renderiza
"<type> question: <instructions>" para a cabeça tokenizada, o Agent recusa uma pergunta sem uma
(«acrescenta o texto que o modelo deve responder»), e a própria identidade de pergunta do Laya já a
conta: o Router._question_schema e o agrupamento em lote deste harness baseiam-se ambos no dict
completo de perguntas, e tests/test_router_batch.py fixa que reformular apenas as instructions
move uma linha para o seu próprio grupo de lote. Então uma instrução reformulada continua a comparar
igual a uma linha de base? Não – e é esse o ponto. «Julga se um reembolso se justifica» e «Sê
conservador e aprova apenas pedidos de reembolso explícitos» fazem perguntas diferentes, e o gate de
métricas só consegue notar quando a diferença, por acaso, move um número mais do que a tolerância
que nomeaste. Nomear uma opção choice é o mesmo argumento: criteria é o espaço de decisão, e as
verificações metamórficas em research/eval/metamorphic.py existem porque renomear uma etiqueta
muda respostas.
Nada no texto da instrução é normalizado, exceto o único passo que o próprio motor aplica: um
instructions que não seja string é hasheado como json.dumps(ins, ensure_ascii=False),
correspondendo a Agent._to_internal. Por isso o espaço em branco e a redação contam ambos, e uma
reformulação que um humano considera uma revisão de texto é tratada como uma nova experiência. É a
predefinição honesta – a alternativa é uma heurística de semelhança entre uma execução e a sua
linha de base, e nenhum sistema de avaliação de uso comum tem uma.
Nada que transporte tempo é registado, por isso um relatório continua reproduzível byte a byte para um runner fixo.
REPORT_SCHEMA, questions_fingerprint(dataset) e file_fingerprint(path) são públicos, por isso
um autor de chamadas que conduza laya.evals.evaluate diretamente obtém a mesma identidade que uma
execução da CLI.
Linha de base e gate de CI
- Mantém o conjunto de dados, um relatório de linha de base (a saída
--jsonque reviste) e as tolerâncias juntos, em commit, para que uma alteração seja um diff revisível.--tolerance METRIC=VALUEé o desvio absoluto máximo permitido para essa métrica. laya-evals run ... --baseline baseline.json --tolerance ...sai com código não-zero em caso de desvio, por isso encaixa na CI sem alterações.laya.evals.EvalReport.compareeassert_regressionexpõem a mesma lógica para testes.
O gate de métricas responde a «os números moveram-se». Não consegue responder a «eram os mesmos
números», porque o compare lê overall e só overall – por isso uma linha de base registada
contra um conjunto de dados passaria um candidato pontuado noutro, com aritmética idêntica.
EvalReport.comparable_to fecha isso: compara schema, dataset_sha256 e questions_sha256, e
run --baseline e compare continuam a imprimir cada delta, e depois falham com uma saída não-zero
nomeando a chave e ambos os valores:
FAIL: baseline is not comparable: dataset_sha256 (dataset bytes): baseline is <sha>, this run is <sha>
Uma chave em falta de um dos lados é desconhecida, não um conflito, por isso todos os relatórios
escritos antes de a identidade existir continuam a comparar exatamente como antes. Isso inclui a
linha de base do gate agendado abaixo, que vem de research/eval/ e não tem nenhum config.schema.
Duas superfícies de CI usam isto:
- uma tarefa sem pesos em
.github/workflows/ci.ymlcorretests/test_evals.pyetests/test_evals_api.py, por isso a matemática das métricas, o parsing do conjunto de dados e a CLI ficam cobertos em cada PR sem descarregar um checkpoint; .github/workflows/evals.ymlcorre semanalmente, antes de uma publicação e a pedido: avalia o checkpoint inglês na suite MASSIVE inglesa e compara comresearch/results/eval_english_51_languages.jsoncom as tolerâncias emresearch/evals/thresholds.json. Envia o relatório como artefacto e não bloqueia um PR.
O harness é determinístico para uma revisão de checkpoint fixa, por isso um relatório é
reproduzível. O run registra o conjunto de dados, o modelo e o dispositivo, mais os factos de
temporização da execução, no bloco config do relatório,
e revisions: o commit a partir do qual foi efetivamente carregado cada checkpoint que respondeu.
--revision <SHA> fixa esse commit para todos os checkpoints que a execução carrega, e --revision english=<SHA> fixa um checkpoint (repetível) — que é a forma que uma execução de encaminhamento
automático quer, já que os três checkpoints são três repositórios e um commit não pode existir em
todos eles. Deixado sem fixar, a execução toma o ramo predefinido do checkpoint e o relatório
continua a dizer que commit respondeu, por isso um desvio de linha de base pode ser atribuído aos
pesos ou ao código. laya/revisions.py publica SHAs de commit revistos em PINNED_REVISIONS para
autores de chamadas que queiram optar por eles. Com --onnx, só um --revision <SHA> simples se
aplica, ao download da config e do tokenizer.
Acrescentar o conjunto etiquetado real
Coloca um JSONL em research/evals/ e uma linha de base revista ao lado dele, e depois aponta um
workflow (ou research/evals/check_regression.py) a ambos. O formato é o mesmo da fixture; nada no
harness sabe do MASSIVE.