Treinei um classificador no Mac com MPS e deixei um portão decidir se ele entra
Ajustei o Laya num M3 Max para servir de fallback de intenção na voz da casa: 2,5 horas por rodada, memória MPS sem limite e um portão que barrou a rodada com a maior nota.
A voz da minha casa funciona com Home Assistant. Quando as regras não reconhecem uma frase, quero um plano B que escolha entre 77 categorias (labels) e indique a probabilidade de cada escolha. Sem essa probabilidade, não consigo decidir se o sistema deve agir. A opção mais óbvia era um modelo pequeno, treinado para as intenções da casa e executado localmente.
Treinei no M3 Max usando PyTorch MPS, o sistema da Apple para fazer cálculos de IA com a GPU. Funcionou, mas o tempo e o uso de memória não bateram com minha previsão.
De onde parti
A base é laya-multilingual, um modelo aberto da convaiinnovations. Ele usa um encoder mmBERT-base multilíngue, uma parte que lê as opções e ajuda a escolher uma. O modelo tem 322 milhões de parâmetros, valores internos ajustados durante o treino.
Sem treino, em 10 de outubro, o modelo acertou 32% nas ferramentas das intenções da casa e gerou falsos positivos em 54% dos casos. Assim, não servia. Num teste de 9 de outubro, depois do primeiro ajuste fino, a acurácia no conjunto held-out passou de 0,317 para 0,917. O ECE ficou em 0,08 e o p50 em 23 ms. Ajuste fino é um treino adicional de um modelo já existente. Held-out é um conjunto separado, que não entra no treino, usado para avaliar o modelo. ECE mede o erro de calibração: a diferença entre a confiança indicada pelo modelo e sua taxa real de acertos. P50 é o tempo em que metade das respostas já chegou.
2,5 horas em vez de 1
Uma rodada completa usa cerca de 9,1 mil itens, 77 labels e 4 épocas, cada uma com cerca de 4.593 micro-etapas de treino. A 133 micro-etapas por minuto, leva cerca de 2,5 horas. Eu tinha previsto uma hora.
A configuração usa soft cross-entropy como função de perda, opções embaralhadas, dispositivo
mps, micro-lote de 2 e acumulação de gradiente de 32. A função de perda mede o erro durante o
treino. Micro-lote é a quantidade de exemplos processados de uma vez. O lote pequeno foi
necessário: a memória do MPS não tem limite. Com micro-lote de 8, chegou a 39 GB.
Tentei limitar o uso com as razões de watermark do MPS e errei duas vezes antes de conseguir.
Definir apenas a razão alta falha com invalid low watermark ratio 1.4, porque o valor padrão
da razão baixa ficava acima da alta. Com a razão alta em 0,5, o treino excedeu o limite:
MPS allocated: 17.97 GiB, max allowed: 18.72 GiB. A combinação de alta em 0,8 e baixa em 0,65
funcionou em 16 de outubro.
O pior erro não gerou aviso. Uma versão anterior do conjunto tinha 76 labels. O head budget (512/768), definido para menos labels, ignorou em silêncio todas as 8.800 linhas. Não apareceu erro nem aviso. Aumentei os valores para 1024/1280.
O portão
Uma rodada só vira o modelo de produção se passar por estas regras automáticas:
- acurácia de pelo menos 0,90 no conjunto held-out;
- acurácia de 1,0 nas falas reais;
- ECE de no máximo 0,10;
- taxa de ações causadas por falsos positivos de no máximo 0,01;
- taxa de ações erradas de no máximo 0,02.
Uma rodada aprovada troca o checkpoint ativo sozinha. O checkpoint é o arquivo com o estado treinado do modelo. Por isso, cada teste treina numa pasta de staging, uma área temporária separada da produção.
A rodada de 21/10 com 99,1% foi rejeitada: errou no conjunto de falas reais.
Ver dados
| Spike (09/10) | 91,7% |
|---|---|
| Promovida (16/10) | 98,7% |
| Rejeitada (21/10) | 99,1% |
| Promovida (21/10) | 97,1% |
A rodada promovida em 16 de outubro teve held-out de 0,987, acerto de 1,0 nas falas reais, ECE de 0,013, falso positivo de 0,0 e ação errada de 0,014. O portão barrou a rodada de 21 de outubro, embora ela tivesse o melhor held-out: 0,991. Ela acertou apenas 0,889 nas falas reais, com 3 erros em 27, e teve taxa de falsos positivos de 0,074.
A rodada aprovada no mesmo dia ficou com held-out de 0,971, acerto de 1,0 nas falas reais, ECE de 0,016, falso positivo de 0,008 e ação errada de 0,0199. Esse último valor ficou quase no limite de 0,02.
Duas rodadas de 21/10. A rejeitada errou 3 das 27 frases.
Ver dados
| Rejeitada | 88,9% |
|---|---|
| Promovida | 100% |
O conjunto held-out vem da mesma distribuição de dados usada no treino. Ele mede quanto o modelo aprendeu desse conjunto. As falas reais mostram se ele entende o que realmente digo. A rodada com a maior nota foi justamente a que o portão barrou.
O que ainda me morde
Em 17 de outubro, a primeira chamada ficou lenta. Parecia que o modelo estava ocioso. O problema era pressão de memória: o processo mostrava RSS de 0,07 GB e memória física ocupada de 2,73 GB. A memória swap estava em 19,0 de 20,5 GB e a média de carga em 13,9. O RSS, que mede parte da memória usada pelo processo, me enganou de novo.
Um deploy, ou publicação de uma nova versão do serviço, reconstruiu o ambiente Python e matou uma rodada mais longa depois de cerca de 4 horas. Com rodadas tão demoradas, eu protegeria o ambiente de treino para que nenhum deploy pudesse alterá-lo.
