mizorewww

Laya-MLX

Os checkpoints pré-treinados do Laya portados para MLX para inferência local em Apple silicon, sem PyTorch nem API na nuvem. Um port independente, não um lançamento oficial da Convai Innovations.

Verificado em 2026-10-05

Laya MLX jogando Snake — decisões reais, velocidade original

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 noul aceitam apenas chaves false/true (incluindo chaves booleanas do Python). O parâmetro opcional labels={"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 None geram erro do chamador; erros de pergunta nomeiam a pergunta.
  • Toda resposta adiciona answer_confidence, a probabilidade calibrada máxima entre as opções. O confidence existente 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.
  • usage adiciona state_tokens, state_tokens_dropped (a maior perda entre as perguntas), truncated e truncated_questions. usage.options aparece apenas para perguntas cujos intervalos de tokens de opção colidem, relatando total, distinct e tokens_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 respeita Router(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:

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.