
Decisões tipadas de pesos abertos, rodando nativamente em Apple Silicon.
13.4 ms de mediana ponta a ponta para uma decisão tipada curta em inglês. 7.4 ms com o checkpoint multilíngue. 0 tokens de saída. Inferência MLX local, sem PyTorch, runtime do Transformers ou API na nuvem.
Chinês · Benchmarks · Demo do Snake · Pesos no Hugging Face
O GIF é uma renderização em velocidade original de uma execução real do Snake local. Cada movimento chama o Laya; a camada visível de segurança de ciclo pode corrigir propostas inseguras. Os números de latência acima são o benchmark separado de uma pergunta da API, não o tempo de quadro do laço de três perguntas do Snake. Assista ao MP4 de 30 segundos · Velocidade e estabilidade do Snake.
Início rápido
pip install laya-mlx
import laya_mlx as laya
agent = laya.load("aac6fef/laya-mlx")
result = agent.predict(
"I was billed twice. Please refund the duplicate.",
{
"department": {
"type": "choice",
"instructions": "Who should handle this?",
"criteria": ["billing", "technical", "sales"],
}
},
)
print(result["answers"]["department"])
Apple Silicon, Python 3.11+, macOS 14+. O primeiro carregamento baixa o checkpoint; a inferência posterior é totalmente local. O ambiente medido é macOS 27.2, Python 3.12.13 e MLX 0.32.2. Essa versão do MLX fornece wheels para macOS 14, 15 e 26; o instalador local selecionou a wheel do 26. Versões mais antigas de macOS compatíveis não foram testadas nesta máquina.
Rode a demo de terminal:
pip install 'laya-mlx[demo]'
hf download aac6fef/laya-multilingual-mlx
laya-snake
Baixe uma vez antes da demo offline. Use um terminal com pelo menos 104 × 35 células. Espaço pausa, ↑/↓ muda a velocidade, R reinicia e Q encerra. laya-snake --max-speed toma uma decisão nova a cada movimento, sem cadência. Gravação, controles e significados exatos das métricas.
laya-snake --optimize --max-speed ativa o caminho testado de compilação e reúso de prefixo: 75.40 movimentos/s em 2,400 movimentos, zero mortes e 2 intervenções de segurança visíveis no teste pareado no M3 Max. Isso foi cerca de 6.5% mais rápido que o controle eager da mesma execução. Evidências de jogabilidade, desempenho e correção.
Desempenho no M3 Max
| FP16, ponta a ponta | Laya 421M | Multilíngue 322M |
|---|---|---|
| Uma pergunta curta, P50 | 13.42 ms | 7.39 ms |
| Uma pergunta curta, P95 | 13.92 ms | 7.79 ms |
| Vazão de 50 perguntas | 146.8 q/s | 395.0 q/s |
| Alocação máxima do MLX, uma pergunta curta | 943.6 MiB | 687.6 MiB |
M3 Max, 40 núcleos de GPU, 128 GiB de memória. A medição de tempo inclui preparação do prompt, tokenização, tensores, inferência sincronizada, calibração e formatação do resultado; o carregamento do modelo fica de fora. A medição de 50 perguntas usa batch_size=64; a API usa 16 por padrão. Comprimentos, contagens de perguntas e condições de execução diferentes alteram a latência. Método completo e cada amostra de tempo.
Fidelidade do port: os três checkpoints corresponderam à resposta selecionada do upstream em 63/63 perguntas de validação tanto em FP32 quanto em FP16 — 378/378 comparações. Cada configuração também passou em 100 chamadas repetidas, finitas e determinísticas, com zero crescimento medido de memória ativa. Isso mede a fidelidade nesses fixtures, não a precisão em toda pergunta possível. Erros de probabilidade e validação.
Por que decisões tipadas?
O software muitas vezes precisa de uma escolha, uma pontuação de rubrica ou uma probabilidade. O Laya responde a essas perguntas restritas em uma passada direta bidirecional, sem decodificação token a token nem JSON gerado.
state + typed question → bidirectional encoder → decision heads → probabilities
choice: probabilidades sobre opções nomeadas.score: probabilidades sobre níveis ordenados de rubrica e sua pontuação esperada.noul: P(true) para uma proposição.
As linhas de pergunta são agrupadas em lote de forma independente. Suas representações no encoder bidirecional dependem tanto do state quanto da pergunta; este runtime não afirma codificar o state uma vez e reutilizar seus estados ocultos entre perguntas arbitrárias.
O encoder, o Transformer de decisão, a cabeça de pontuação e a cabeça de ação rodam todos em MLX. A tokenização usa o tokenizer Rust do Hugging Face. Os pesos pré-treinados originais, a formatação de perguntas, a calibração e o esquema de saída são mantidos. Este é um port independente para MLX, não um lançamento oficial da Convai Innovations.
Checkpoints compatíveis
| Modelo | Encoder | Parâmetros | Limite de contexto | Finalidade |
|---|---|---|---|---|
convaiinnovations/laya |
ModernBERT-large | 421M | 512 | Inglês |
convaiinnovations/laya-multilingual |
mmBERT-base | 322M | 1,024 | Entrada multilíngue |
convaiinnovations/laya-typed-decisions |
ModernBERT-large | 421M | 1,024 | Fluxos de decisões tipadas do upstream |
O contexto inclui instruções, opções e state. Os três usam os pesos originais, a formatação de prompt, a calibração de temperatura e o esquema de saída. Este repositório fornece inferência e conversão; o treinamento RLCD e o ajuste fino permanecem no projeto upstream. É um port independente, não um lançamento oficial da Convai Innovations.
Checkpoints FP16 pré-convertidos estão publicados no Hugging Face:
Carregue-os diretamente com laya.load("aac6fef/laya-mlx"), ou use os IDs de checkpoint originais acima. Cada checkpoint publicado inclui seu model card, resultados de validação, proveniência, licença e somas de verificação dos arquivos. Todos os 36 arquivos publicados passaram na verificação remota estrita de somas de verificação; revisões fixadas e hashes de pesos estão registrados em hub-publication.json.
Instalação para desenvolvimento
gh repo clone mizorewww/laya-mlx
cd laya-mlx
uv sync --extra demo
uv run --extra demo laya-snake
Ou instale a revisão mais recente do GitHub com pip install 'git+https://github.com/mizorewww/laya-mlx.git'. Os pesos do modelo são baixados à parte e ficam excluídos do Git.
API Python
import laya_mlx as laya
agent = laya.load("aac6fef/laya-mlx", dtype="float16")
result = agent.predict(
"I was billed twice. Please refund the duplicate today.",
{
"department": {
"type": "choice",
"instructions": "Which team should handle this request?",
"criteria": {
"billing": "invoices, payments, refunds",
"technical": "bugs and outages",
"sales": "new purchases",
},
},
"urgency": {
"type": "score",
"instructions": "How urgent is this request?",
"criteria": ["not urgent", "soon", "critical"],
},
"refund": {
"type": "noul",
"instructions": "Does the customer ask for money back?",
},
},
)
print(result["answers"])
system_one é um alias de predict. Os states podem ser texto, dicionários JSON ou listas de conversa. choice aceita um dicionário ou uma lista de rótulos únicos; score retorna o nível de rubrica esperado, com base zero; noul retorna P(true). Os resultados mantêm o arredondamento de quatro casas decimais do upstream, action.act_probability e os campos de uso de tokens.
A precisão padrão é FP16. Use dtype="float32" para maior concordância numérica. As probabilidades podem diferir um pouco entre precisões mesmo quando o rótulo selecionado coincide; veja os erros medidos em BENCHMARKS.md. BF16 pode ser solicitado, mas não faz parte da matriz de validação publicada.
Seguindo o upstream v0.3.5, as temperaturas de calibração ajustadas são limitadas a [0.5, 5.0] antes do uso: o bucket choice:11+ fornecido é 0.1006, o que aguçaria os logits ~10x e reportaria um cara ou coroa como quase certeza. Os valores brutos do checkpoint continuam disponíveis como agent.temperature_raw e agent.temperature_by_options_raw, e um RuntimeWarning nomeia cada bucket limitado no carregamento.
batch_size=16 limita o número de perguntas por passada direta; requisições maiores são processadas em blocos. Aumente quando a memória permitir. device="gpu" ou device="cpu" seleciona um dispositivo explicitamente; caso contrário, usa-se o dispositivo padrão do MLX.
Para cargas de trabalho repetidas, opte por compile=True, pad_to_multiple=16 e cache_prompts=True ao carregar um Agent. O cache de prefixo é limitado a 128 perguntas e compartilha a tokenização do state na CPU, enquanto cada pergunta ainda recebe seu próprio cálculo no encoder. A compilação tem um custo de primeiro uso e especialização de forma; o padding pode deixar algumas cargas de trabalho mais lentas. As três opções vêm desativadas por padrão. Ablação e uso medidos no Snake.
agent = laya.load("./models/laya", dtype="float32", batch_size=32)
# Select one checkpoint inside upstream's bundled repository:
multi = laya.load("convaiinnovations/laya", subfolder="multilingual")
# Pin a Hub revision for reproducibility:
agent = laya.load(
"convaiinnovations/laya",
revision="c5d78730f3493e4fe16d61507ef4b78eef7318cf",
)
O carregamento valida cada nome e forma de parâmetro. Encoders não compatíveis e escalonamento RoPE não padrão falham explicitamente. O padrão de atenção global/local do ModernBERT, a fronteira inclusiva da janela deslizante, as bases RoPE local/global distintas e o comportamento de normalização da primeira camada são preservados.
Roteamento de idioma e presets
from laya_mlx import Router, triage_questions
router = Router(dtype="float16", max_loaded=2)
result = router.predict({"message": "发票被重复扣款,请退款。"}, triage_questions())
print(result["routing"]) # multilingual
# Choose the specialized checkpoint explicitly:
result = router.predict(state, questions, task="typed_decisions")
O roteador, as heurísticas de idioma, os helpers de e-mail e os presets de aplicação são adaptados do upstream. Router(preload=True) mantém os três checkpoints residentes; attach, preload, unload, lang= explícito e model= explícito são compatíveis. O ciclo de vida do modelo é protegido por um lock reentrante, então threads concorrentes compartilham um único Agent carregado em vez de criar duplicatas; a inferência em si não é serializada. A detecção de fluxo de trabalho de decisões tipadas continua opt-in. O port preserva as limitações do modelo: checkpoints em inglês não substituem o checkpoint multilíngue, e a confiança não garante precisão.
Idiomas de escrita latina não identificados (romeno, polonês, tcheco, turco, …) são roteados para o checkpoint multilíngue apenas por suas letras não inglesas, em vez de serem silenciosamente presumidos como inglês. detect_language(state) relata as evidências: language_undecided e diacritic_rate junto com language e is_english.
Pré-seleção de grandes conjuntos de escolha
As opções de choice compartilham um único orçamento de tokens head_max_len, então uma pergunta com centenas de rótulos deixa só alguns tokens por rótulo. predict_shortlist embute o state e cada rótulo, mantém os k primeiros por similaridade de cosseno e roda um único predict no conjunto reduzido. Isto é opt-in: Agent.predict ainda pontua todo critério que recebe.
import laya_mlx as laya
agent = laya.load("aac6fef/laya-mlx")
embed_fn = laya.embed_fn_from_agent(agent) # mean-pools the loaded encoder; no extra weights
result = laya.predict_shortlist(agent, state, questions, embed_fn, k=20)
print(result["shortlist"]) # which labels were kept, with cosine scores
Um bi-encoder dedicado passado como embed_fn costuma pré-selecionar melhor que o próprio encoder do checkpoint de decisão. As probabilidades em um choice pré-selecionado são apenas sobre os rótulos mantidos.
Linha de comando
uv run laya-mlx predict \
--model aac6fef/laya-mlx \
--state-file examples/state.json \
--questions examples/questions.json
uv run laya-mlx predict \
--model aac6fef/laya-multilingual-mlx \
--state '发票被重复扣款,请退款。' \
--questions examples/questions.json
Correções selecionadas do upstream após a v0.3.5
O runtime incorpora seletivamente correções de entrada, roteamento e e-mail do upstream
4aa6761 (árvore de código da v0.3.23). Isto não adiciona as APIs de lote, documentos
longos, hooks ou servidor do upstream. A paridade da arquitetura neural continua testada
contra 573e5b6.
- Listas de conversa cronológicas mantêm seus tokens mais recentes quando o contexto enche; strings e dicionários mantêm o início. O cache de prefixo usa essa mesma regra.
- Os critérios de
noulaceitam apenas chavesfalse/true(incluindo chaves booleanas do Python). O parâmetro opcionallabels={"false": "no", "true": "yes"}muda as palavras mostradas ao modelo, enquanto a resposta continua P(true). Chaves inválidas agora geram erro em vez de serem ignoradas. - Instruções que não são strings preservam Unicode. Instruções vazias, níveis de score
nulos e um state
Nonegeram erro do chamador; erros de pergunta nomeiam a pergunta. - Toda resposta adiciona
answer_confidence, a probabilidade calibrada máxima entre as opções. Oconfidenceexistente mantém seu significado baseado em entropia para choice/score e a probabilidade máxima para noul. Nenhum dos campos garante precisão em uma tarefa nova. usageadicionastate_tokens,state_tokens_dropped(a maior perda entre as perguntas),truncatedetruncated_questions.usage.optionsaparece apenas para perguntas cujos intervalos de tokens de opção colidem, relatandototal,distinctetokens_per_option. Isto relata distinções perdidas; não as recupera nem remove o viés de posição.Router.preload()incremental preserva os modelos residentes;preload([])não faz nada. Dicas em branco ou neutras de idioma caem na detecção, e texto latino indecidido respeitaRouter(default=...). A detecção examina valores de string aninhados e texto misto.- A limpeza de e-mail preserva pedidos comuns que mencionam confidencialidade, agradecem ao
destinatário ou começam com
From:, ao mesmo tempo que reconhece rodapés de e-mail multilíngues.
Exportar um checkpoint MLX
uv run laya-mlx convert \
--model convaiinnovations/laya \
--dtype float16 \
--output models/laya-mlx-fp16
uv run laya-mlx predict \
--model models/laya-mlx-fp16 \
--state-file examples/state.json \
--questions examples/questions.json
A exportação contém model.safetensors, configurações do encoder e do agent, arquivos do tokenizer e mlx_config.json. Diretórios de saída existentes nunca são sobrescritos. Isto é uma conversão de nome de parâmetro/dtype, não quantização ou retreinamento. Os checkpoints de origem já armazenam pesos FP16; escolher FP32 aumenta a precisão aritmética, não a precisão dos pesos de origem.
Testes e benchmarks
uv sync --extra dev --extra reference --extra benchmark --extra demo
source .venv/bin/activate
gh repo clone NandhaKishorM/laya .upstream
git -C .upstream checkout 573e5b62696ba441230cd6be71d593331b5d23af
pytest -q
python -m benchmarks.download
python -m benchmarks.validate --repeats 100
python -m benchmarks.run --iterations 50 --warmup 5
python -m benchmarks.accuracy --per-class 64
python -m benchmarks.report
Rode as medições de GPU em sequência. Os testes unitários usam modelos aleatórios pequenos e incluem comparações diretas com o Transformers e com a cabeça de decisão fixada do upstream. A validação com checkpoint real testa tokenização, logits, probabilidades calibradas, saídas repetidas e crescimento de memória ativa. O benchmark roda cada backend/checkpoint em um processo novo e armazena cada amostra de tempo em benchmarks/results. O relatório completo explica as fronteiras de medição e as diferenças de precisão.
O GitHub Actions roda testes de CPU com modelos pequenos em um runner macOS arm64. Os benchmarks completos de GPU com checkpoint são medidos localmente e não fazem parte da CI hospedada.
Pesquisa de desempenho
As investigações de desempenho incluem tanto análise matemática quanto experimentos locais independentes:
- Pesquisa de desempenho inicial: gargalos de implementação, despacho de kernels do MLX e um plano de experimento controlado.
- Investigação matemática de mais um ganho de 10×: orçamentos aritméticos, limites condicionais de banda, espectros de pesos reais, reúso exato e projetos de modelos menores.
- Investigação de engenharia: compilação medida, quantização, seleção da cabeça final, kernels Metal personalizados e multiplicações de matrizes representativas.
experiments/ contém os scripts de pesquisa e suas medições brutas. Os resultados de desempenho e validação do runtime publicado estão em BENCHMARKS.md; cada variante experimental tem seus próprios resultados de tempo e correção.
A investigação atual não sustenta mais um ganho universal de 10× com os mesmos checkpoints. Casos selecionados mostram ganhos pareados de mediana de aproximadamente 1.03–1.08×; o relatório de engenharia traz os intervalos de incerteza, os resultados de fidelidade da quantização e as medições do kernel Metal personalizado.
Para preparar model cards e exportações verificadas para publicação, instale os extras de referência e rode:
python -m scripts.prepare_hub --account YOUR_HF_USERNAME
hf upload YOUR_HF_USERNAME/laya-mlx models/hub/laya-mlx . --exclude '.cache/*'
O script de preparação confere cada tensor exportado contra sua origem FP16 original. Envie as outras duas pastas preparadas da mesma forma e depois use hf cache verify REPO_ID --local-dir EXPORT_PATH para conferir os arquivos remotos.
Atribuição e licença
Apache-2.0; veja LICENSE e NOTICE. Laya e seus pesos pré-treinados são da Convai Innovations e dos contribuidores do upstream. A construção do prompt, a formatação da saída, o roteamento de idioma, os utilitários de e-mail e os presets são adaptados de NandhaKishorM/laya no commit 573e5b62696ba441230cd6be71d593331b5d23af. A arquitetura neural é reimplementada em MLX seguindo o Laya e o ModernBERT do Hugging Face.
Manutenção e lançamentos
Este projeto segue o comportamento do Laya upstream por meio de uma implementação nativa em MLX. Correções compatíveis com o upstream têm prioridade sobre variantes independentes de modelo, APIs de serviço e demos adicionais. Isto continua sendo um port seletivo, não uma alegação de paridade total com a API do upstream.
Para lançar, atualize a versão em pyproject.toml, laya_mlx/__init__.py e uv.lock,
depois envie uma tag vX.Y.Z correspondente. O GitHub Actions roda a suíte de testes do macOS, valida
a consistência de versão, constrói e confere a wheel e a distribuição de código-fonte, publica-as no
PyPI usando o segredo PYPI_API_TOKEN do repositório e cria um release no GitHub.
Um teste ou build falho impede a publicação.