Otimizando a carga de trabalho real do Snake
O caminho opt-in adotado combina compilação do MLX, buckets de comprimento de 16 tokens e um cache limitado de prefixos de perguntas tokenizadas. Ele não altera pesos, não quantiza o modelo, não faz cache de previsões nem reutiliza estados ocultos do encoder bidirecional entre perguntas.
Em um teste pareado de laço completo, o caminho otimizado fornecido alcançou 75.40 movimentos/segundo em 2,400 movimentos, contra 70.82 movimentos/segundo para inferência eager no mesmo teste: 1.065×, ou cerca de 6.5%. Ambos tiveram zero mortes, 2 intervenções de segurança e ações executadas idênticas em 2,400/2,400 passos. O crescimento combinado de memória ativa do MLX após warmup e limpeza de cache foi 0 bytes.
Ative
laya-snake --optimize
laya-snake --optimize --max-speed
A API geral expõe os mesmos controles opt-in:
import laya_mlx as laya
agent = laya.load(
"aac6fef/laya-multilingual-mlx",
compile=True,
pad_to_multiple=16,
cache_prompts=True,
)
As três opções vêm desativadas por padrão, preservando o comportamento eager existente e a configuração do benchmark. A compilação se especializa pela forma de entrada; o primeiro uso e formas novas podem incorrer em custo de compilação. Construa um novo Agent após mudar pesos ou estrutura de módulos. O padding arredonda o comprimento da sequência para o múltiplo solicitado sem exceder o limite de contexto configurado. As máscaras excluem os tokens de padding.
cache_prompts=True mantém no máximo 128 prefixos imutáveis de PreparedQuestion por Agent, incluindo as posições dos marcadores. As chaves de cache incluem a identidade do tokenizer, tokens especiais, tipo de pergunta, opções renderizadas ordenadas, instruções e o orçamento de prefixo. O state é sanitizado e tokenizado uma vez por chamada de prepare, depois concatenado independentemente com cada prefixo de pergunta. Mudanças de pergunta criam ou selecionam o prefixo adequado. Cada pergunta ainda recebe um forward completo do modelo.
Ablação de forma e preparação
Esta demo faz três perguntas por movimento: direção, estimativa de rota segura e estimativa de alcançabilidade da comida. Assim, seu tamanho de lote é 3, e não 1. Em 32 tabuleiros reais gravados e amostrados:
- Os comprimentos de sequência multilíngues foram 59, 61, 63 e 64. Um múltiplo de 16 tokens coloca todos eles em um bucket de 64 tokens.
- Os comprimentos de sequência em inglês foram 66, 68, 69 e 70, mapeando para um bucket de 80 tokens.
- Fazer padding da entrada multilíngue de 64 para 96 adiciona 50% ao trabalho por token; não é o pequeno ajuste de 93 para 96 sugerido pelo fixture separado de texto curto da API.
Os candidatos rodaram em ordem rotativa dentro de cada state idêntico, após visitar cada forma medida uma vez. A tabela contém a latência sincronizada do Agent.predict, incluindo tokenização e conversão da saída, e exclui o trabalho do planejador/UI e o warmup inicial de forma.
| Variante | Multilíngue p50 / p95 (ms) | Inglês p50 / p95 (ms) |
|---|---|---|
| Eager | 9.12 / 10.21 | 21.83 / 26.73 |
| Apenas reúso de prefixo | 8.95 / 9.75 | 21.60 / 25.23 |
| Compilado, comprimento real | 8.67 / 9.66 | 21.27 / 25.99 |
| Compilado, com padding até 96 | 10.92 / 11.72 | 25.66 / 29.88 |
| Compilado + reúso de prefixo | 8.66 / 9.21 | 21.03 / 23.97 |
| Compilado, bucket de carga de trabalho | 8.66 / 9.55 | 21.78 / 26.12 |
| Compilado + bucket + reúso de prefixo | 8.56 / 9.29 | 21.51 / 24.16 |
Os sete candidatos corresponderam às direções propostas e executadas do eager em 32/32 tabuleiros por checkpoint. A diferença máxima nas probabilidades e estimativas exibidas, com quatro casas decimais, foi 0 nessas amostras. Isto é concordância de saída arredondada em amostra finita, não uma alegação de tensores internos de ponto flutuante bit a bit idênticos.
A ablação usou wrappers limitados de preparação de prefixo para triar projetos. O teste de laço completo abaixo usa a implementação real e fornecida da API compile, pad_to_multiple e cache_prompts. Seus testes de correção também comparam IDs e marcadores preparados sob truncamento de state, critérios variáveis, sanitização de máscara e despejo de cache.
O caminho otimizado fornecido também passou na matriz completa de validação com checkpoint real: 63/63 de concordância de resposta selecionada para cada um de três checkpoints em FP32 e FP16 (378/378 no total). Os erros de probabilidade calibrada permaneceram dentro das tolerâncias existentes. Cada configuração passou em 10 chamadas repetidas finitas e determinísticas adicionais, com 0 bytes de crescimento medido de memória ativa. Dados de validação otimizados. Os resultados do caminho eager original com 100 repetições por configuração permanecem em o relatório de benchmark original.
Para o inglês, a compilação com o comprimento real da sequência foi melhor que forçar o bucket maior nesta amostra. A demo usa o multilíngue por padrão; usuários da API geral podem deixar pad_to_multiple=None enquanto ativam a compilação e o reúso de prefixo.
Teste pareado de laço completo
Quatro seeds, 600 movimentos cada, com a ordem dos candidatos alternando por seed. A renderização inclui composição com Rich truecolor e serialização ANSI, e exclui a pintura do emulador de terminal. Cada movimento realiza uma previsão nova. Os resultados são de uma única execução pareada local.
| Seed | Movimentos/s eager | Movimentos/s otimizado | Pontuação (ambos) | Concordância de ações |
|---|---|---|---|---|
| 101 | 68.60 | 78.04 | 20 | 600 |
| 102 | 70.07 | 78.62 | 24 | 600 |
| 103 | 76.75 | 85.62 | 23 | 600 |
| 104 | 68.50 | 63.15 | 16 | 600 |
O caminho otimizado foi mais lento em uma seed. Consequentemente, 6.5% é a melhoria combinada nesta execução medida, e não uma melhoria garantida para todo episódio ou máquina. A varredura ampla de velocidade anterior e este teste pareado posterior são execuções diferentes; suas taxas absolutas não devem ser subtraídas para alegar um ganho de velocidade. Dados completos do laço.
Escolha o modelo usando tanto a jogabilidade quanto a latência
Os dois checkpoints rodaram 20 seeds pareadas × 300 movimentos, com a ordem dos checkpoints alternando a cada seed. Cada episódio usou o mesmo state inicial, a mesma seed do RNG de comida, as mesmas descrições compactas de features e o mesmo escudo de ciclo. O horizonte é fixo; estas são pontuações após 300 movimentos, não partidas completas terminadas em morte ou tabuleiro cheio. Nenhuma renderização de terminal foi incluída nesta comparação de modelos.
| Checkpoint | Sobreviveu / episódios | Movimentos | Pontuação mediana / média | Inferência p50 / p95 (ms) | Intervenções |
|---|---|---|---|---|---|
| laya | 20 / 20 | 6000 | 7.0 / 6.9 | 23.15 / 28.21 | 0 |
| multilingual | 20 / 20 | 6000 | 10.0 / 9.9 | 9.38 / 14.38 | 2 |
O multilíngue fez mais progresso rumo à comida e foi mais rápido nesta carga de trabalho, então ele permanece o checkpoint padrão da demo. O resultado avalia esta política assistida por features, não a qualidade de raciocínio geral nem um modelo de Snake sem auxílio. Cada episódio e inferência.
Reproduza
uv run --extra demo python -m experiments.snake_runtime \
--output artifacts/snake/runtime-multilingual.json
uv run --extra demo python -m experiments.snake_runtime \
--model models/hub/laya-mlx --bucket 80 \
--output artifacts/snake/runtime-english.json
uv run --extra demo python -m benchmarks.snake_optimized \
--output artifacts/snake/optimized-paired.json
uv run --extra demo python -m benchmarks.snake_models \
--episodes 20 --steps 300 --output artifacts/snake/models.json
Rode as medições de GPU em sequência. Baixe primeiro os dois diretórios de modelo locais. A gravação de origem versionada fornece os estados de tabuleiro amostrados exatos. Ablações brutas: multilíngue, inglês, piloto inicial de 96 tokens.
A comparação anterior entre prompt compacto e detalhado alternou a ordem dos prompts em 64 states e encontrou mediana de 11.80 → 9.29 ms após descartar as primeiras 8 iterações de warmup. Seu registro ad-hoc original não armazenou snapshots de tabuleiro, então é evidência de apoio, e não a ablação reproduzível principal. python -m benchmarks.snake_prompt fornece uma versão reproduzível que armazena states, seed, decisões completas e método.
A implementação segue o guia oficial de compilação do MLX: use um callable compilado de vida longa e especialização de forma normal. Ela não usa shapeless=True em código Python de modelo dependente de forma. A documentação atual foi conferida pelo site oficial depois que as requisições da CLI do Context7 falharam com erros de rede.