- Ternlight permite realizar embeddings de texto e busca por similaridade dentro do navegador, sem chamadas ao servidor, possibilitando montar rapidamente uma pequena busca semântica no lado do cliente
- O pacote básico soma 7 MB entre engine e pesos; a variante mini tem 5 MB e roda na CPU, sem GPU
- Depois de instalar
@ternlight/base, basta importar embed e similar para criar um fluxo de busca semântica em cerca de 3 linhas
- A chamada de exemplo ordena os 3 principais resultados em uma lista de receitas, com destaque para cerca de 5 ms e nenhuma chamada de rede
- A demo de busca na documentação do React executa a pesquisa no navegador quando o usuário digita uma pergunta, e é movida pelo tier de 5 MB @ternlight/mini
Embeddings resolvidos dentro do navegador
- Ternlight se apresenta como um “modelo de embeddings de 7 MB”, incorporando texto em milissegundos sem chamar um servidor
-
Características de execução
- Sem chamadas de API
- Engine + pesos: 7 MB
- Variante mini: 5 MB
- Embeddings rápidos: cerca de 5 ms
- Apenas CPU, sem GPU
Instalação e exemplo de uso
- É oferecido como um único pacote npm e pode ser usado sem etapa separada de download de modelo nem servidor
- O comando de instalação é o seguinte
npm install @ternlight/base
- Importe
embed e similar de @ternlight/base para executar uma busca semântica
import { embed, similar } from '@ternlight/base';
similar('easy weeknight dinner ideas', recipes, { topK: 3 });
// → ranked matches · ~5 ms · zero network
- A demo de busca na documentação do React executa a pesquisa no navegador quando o usuário digita uma pergunta, e roda com o tier de 5 MB @ternlight/mini
1 comentários
Comentários no Hacker News
Eu queria rodar um modelo útil no navegador como projeto de hobby, então destilei um pequeno codificador de sentenças a partir do MiniLM e apliquei treinamento com reconhecimento de quantização ternária.
Também escrevi o mecanismo de inferência por conta própria e o distribuí em Rust → WASM SIMD.
Não é um LLM, mas um modelo de embeddings: ao inserir um texto, ele gera um vetor de 384 dimensões, e a relevância entre textos é determinada pela similaridade de cosseno entre dois vetores. Por exemplo, "reset my password" e "I forgot my password" saem com algo como 0,88.
Pode ser usado para busca semântica, correspondência de FAQ/intenção e clustering; por rodar no próprio dispositivo, permite busca semântica rápida assim que a entrada é fornecida, sem depender de APIs.
A demo pesquisa 2 mil documentos do React inteiramente no dispositivo: https://ternlight-demo.vercel.app
No npm há dois níveis: @ternlight/base (7 MB, cerca de 5 ms por embedding, embeddings com melhor desempenho) e @ternlight/mini (5 MB de transferência, cerca de 2,5 ms por embedding), empacotados para Node e navegador.
O repositório inclui detalhes técnicos, licença MIT e pipeline de treinamento: https://github.com/soycaporal/ternlight
Tenho curiosidade se embeddings no dispositivo são realmente úteis e quais casos de uso existem.
Fico curioso se isso ajudaria a fazer com que, quando o usuário digite "pancake", ele encontre crêpe sem eu precisar escrever explicitamente uma entrada de dicionário "pancake = crêpe".
Se entendi corretamente, também queria saber se a estrutura é a biblioteca baixar 5 MB uma vez no início e, depois disso, ser usada mais ou menos como hoje uso Fuse.js.
Também gostaria de saber quão bem ela lida com idiomas além do inglês e se daria para treiná-la com a wiki de tags do OpenStreetMap.
Fico curioso se há alguma comparação com outros modelos de embeddings ultrapequenos. É difícil saber se o MiniLM-L6 foi o ponto de partida por ser um modelo particularmente bom nessa categoria, já que a única métrica fornecida é "Retrieval (SciFact NDCG@10)".
Ainda assim, há uma diferença considerável em relação ao desempenho alegado: no Firefox em um i5-4570, obtenho apenas 35 embeddings por segundo, não 400. Suspeito que possa estar caindo para um caminho sem SIMD, e pretendo testar também o binário Rust nativo.
Legal, mas seria bom colocar um botão para iniciar a demo na landing page. Fiquei bem surpreso ao abrir a página e ouvir a ventoinha girando feito louca.
Seria bom transformar isso em um plugin para Astro ou para um metaframework genérico, que analisasse automaticamente todos os arquivos HTML gerados e criasse um pequeno banco de dados de embeddings.
No front-end, ele poderia ser carregado sob demanda, e talvez o HNSW também pudesse ser armazenado em chunks para carregar só as partes necessárias à consulta de busca.
Por exemplo, algo parecido com https://pagefind.app/, mas oferecendo busca vetorial totalmente estática.
Como meses, talvez anos, se passaram e continua assim, isso me pareceu um sinal meio decepcionante de falta de capacidade para concluir o projeto direito. Pior: recomendei esse projeto como um bom candidato em uma bolsa à qual me candidatei; eles foram selecionados e eu não.
Se alguém souber de uma boa solução nessa área, ou se eu estiver errado sobre SQLite-vec, gostaria que me dissesse. No nosso SSG, praticamente decidimos trabalhar alguns meses em outra infraestrutura e, se isso ainda não estiver pronto, vamos fazer nós mesmos.
Isso poderia ser uma adição bem interessante a um projeto de busca DuckDB HNSW que vi aqui antes: https://github.com/jasonjmcghee/portable-hnsw
É realmente interessante que a busca aconteça usando requisições HTTP de intervalo sobre arquivos Parquet hospedados estaticamente.
Acho que coisas assim podem evoluir para um ecossistema de busca relativamente aberto e distribuído, não controlado por grandes empresas.
https://news.ycombinator.com/item?id=27016630
Isso é muito legal e pode ser a peça que faltava para algo que eu queria criar antes.
Com https://github.com/npiesco/absurder-sql, dá para persistir todo o corpus original dentro do navegador em IndexedDB/SQLite.
Depois, como em https://weaviate.io/blog/chunking-strategies-for-rag, em vez de indexar tudo antecipadamente, dá para usar o Ternlight para gerar e armazenar embeddings em cache sob demanda.
Isso também permitiria busca híbrida, combinando o FTS5/BM25 do SQLite nativo com a busca semântica do Ternlight por Reciprocal Rank Fusion.
Muito bem feito.
Ele é promovido como tendo 7 MB, mas também há uma versão mini de 5 MB.
Pelo visto, a mini usa internamente um vetor de 256 elementos em vez de 384 para reduzir espaço e, no fim, projeta para 384 por compatibilidade.
O tamanho cai em um terço, mas a perda não é linear; mesmo usando um caminho de dados menor, a perda de informação parece ser menor que um terço.
Projeto bacana.
Já tentei algo parecido antes: http://sol.quipu-strands.com/
Eu queria carregar um modelo de embeddings no navegador e ordenar textos semanticamente.
Peguei pesos ONNX (MPNet, MiniLM) do HuggingFace, gerei embeddings com Transformers.js e depois usei clusterizadores do scikit-learn rodando em pyodide dentro da página. Tudo rodava do lado do cliente, e fiquei surpreso por funcionar perfeitamente.
A demo se comporta de forma bem estranha. Por exemplo, ao buscar "how to use typescript with createContext", os principais resultados são apenas itens de typescript, então parece que a busca por similaridade falhou.
Obrigado. Modelos locais um dia trarão privacidade, e já conheço um ótimo caso de uso que combina muito bem com modelos pequenos de embeddings como este: busca barata e rápida em bancos de dados de produtos.
No meu caso, depender da CPU também é uma vantagem.
É possível gerar previamente os embeddings, que levam 30 segundos, e enviá-los para o navegador?
Depois disso, a inferência é rápida e boa.