Tokenizador no Hugging Face: Princípios e como usar em Java
1. Visão geral
Ao trabalhar com modelos NLP no Hugging Face, o primeiro passo é sempre a tokenização — transformar texto bruto em uma sequência de números que o modelo entende. Este artigo explica como o tokenizador funciona, as estratégias de divisão de tokens comuns (BPE, WordPiece, SentencePiece), e como chamar HuggingFaceTokenizer diretamente do Java via biblioteca DJL sem precisar executar Python. Ao terminar, você saberá construir um TokenizerService em Spring Boot para codificar/decodificar texto, controlar padding/truncation, e escrever testes para isso.
2. O que é um tokenizador e por que é importante
Um tokenizador faz duas coisas: divide uma sentença em tokens (que podem ser palavras, subwords ou caracteres), depois mapeia cada token para um ID numérico inteiro no vocabulário do modelo. O modelo nunca “vê” letras — ele só vê essa sequência de IDs.
Por exemplo, a sentença "Pedido de serviço processa pagamento" com o tokenizador do bert-base-uncased pode gerar:
tokens: ["pedido", "de", "serviço", "pro", "##cessa", "pagamento"]
ids: [2344, 2326, 1045, 2135, 7899, 8821]
A notação ##cessa significa que este token está ligado ao token anterior (subword). É por isso que o número de tokens é geralmente maior que o número de palavras — isso é importante quando você calcula o custo de chamar uma API ou define o max_length do modelo.
Nota: Cada modelo pré-treinado vem com seu próprio tokenizador, treinado junto com o modelo no mesmo vocabulário. Usar o tokenizador errado (por exemplo, tokenizador BERT para modelo GPT) produzirá IDs sem sentido e o modelo fará previsões completamente erradas sem relatar nenhum erro.
3. Estratégias de tokenização comuns
3.1. BPE (Byte-Pair Encoding)
Usado em GPT-2, RoBERTa. Começa com caracteres e repetidamente mescla os pares de caracteres que aparecem com mais frequência em um novo token, até atingir o tamanho do vocabulário desejado.
3.2. WordPiece
Usado em BERT. Similar ao BPE, mas seleciona pares para mesclar com base em probabilidade estatística em vez de frequência bruta. Tokens sub-palavras são marcados com ## para indicar que estão ligados à palavra anterior.
3.3. SentencePiece
Usado em T5, XLNet, ALBERT. Trata espaços em branco como um caractere normal (representado como ▁), por isso funciona bem com idiomas sem limites de palavra claros, como japonês e chinês.
Você não precisa implementar esses algoritmos — o Hugging Face já os empacotou no arquivo tokenizer.json que vem com cada modelo. Seu trabalho é apenas carregar e chamar.
4. Adicionar dependência
Para usar o tokenizador diretamente em Java (sem passar por um servidor Python), adicione a biblioteca DJL Hugging Face Tokenizers:
<dependency>
<groupId>ai.djl.huggingface</groupId>
<artifactId>tokenizers</artifactId>
<version>0.28.0</version>
</dependency>
Esta biblioteca lê diretamente o arquivo tokenizer.json padrão do Hugging Face, sem a necessidade de executar JVM via JNI complexo.
5. Codificar texto básico
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
import ai.djl.huggingface.tokenizers.Encoding;
import java.util.Arrays;
public class TokenizerDemo {
public static void main(String[] args) throws Exception {
HuggingFaceTokenizer tokenizer = HuggingFaceTokenizer.newInstance("bert-base-uncased");
Encoding encoding = tokenizer.encode("Pedido de serviço processa pagamento");
System.out.println(Arrays.toString(encoding.getIds()));
System.out.println(Arrays.toString(encoding.getTokens()));
}
}
HuggingFaceTokenizer.newInstance(...): carrega o tokenizador do Hugging Face Hub (ou cache local) pelo nome do modelo.encode(...): retorna um objetoEncodingcontendoids,tokens,attentionMask.- Na primeira execução, o arquivo
tokenizer.jsonserá baixado para~/.djl.ai/cache; nas execuções seguintes, o cache é usado e não é necessário acesso à rede.
Saída de referência:
[101, 2344, 2326, 16024, 7909, 102]
[[CLS], pedido, de, serviço, processa, pagamento, [SEP]]
Note os dois tokens especiais [CLS] (início de sentença) e [SEP] (fim de sentença) que são inseridos automaticamente — este é um padrão BERT, não um erro.
6. Decodificar de volta
long[] ids = encoding.getIds();
String decoded = tokenizer.decode(ids);
System.out.println(decoded);
[CLS] pedido de serviço processa pagamento [SEP]
decode é útil para debug: visualizar exatamente o que o modelo “vê” após tokenização, ou ao processar saída gerada por modelos generativos.
7. Padding, truncation e lote
Quando você envia múltiplas sentenças para o modelo simultaneamente, elas devem ter o mesmo comprimento (padding) e não exceder o limite do modelo (truncation).
import java.util.Map;
HuggingFaceTokenizer tokenizer = HuggingFaceTokenizer.newInstance(
"bert-base-uncased",
Map.of(
"padding", "true",
"truncation", "true",
"maxLength", "16"
));
Encoding[] batch = tokenizer.batchEncode(new String[] {
"Pedido colocado com sucesso",
"Pagamento falhou devido ao saldo insuficiente"
});
for (Encoding e : batch) {
System.out.println(Arrays.toString(e.getIds()));
System.out.println(Arrays.toString(e.getAttentionMask()));
}
padding=true: sentenças curtas recebem tokens[PAD](ID 0) para igualar o comprimento das sentenças mais longas no lote.truncation=true+maxLength=16: sentenças mais longas que 16 tokens são truncadas.attentionMask: sequência de 1/0 marcando qual token é real (1) ou padding (0) — o modelo usa essa máscara para ignorar partes de padding ao calcular atenção.
[101, 2344, 2872, 4652, 102, 0, 0, 0]
[1, 1, 1, 1, 1, 0, 0, 0]
Nota: Se esquecer de definir
attentionMaskao passar para o modelo, a parte de padding pode ser incluída no cálculo de atenção, causando saídas incorretas sem relatar nenhum erro em tempo de execução.
8. Integração com Spring Boot
Encapsule o tokenizador em um bean compartilhado para evitar recarregar a cada requisição (carregar o tokenizador do Hub/cache leva 100–300ms, não deve ser feito na thread de requisição).
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
@Service
public class TokenizerService {
private final HuggingFaceTokenizer tokenizer;
public TokenizerService() throws IOException {
this.tokenizer = HuggingFaceTokenizer.newInstance(
"bert-base-uncased",
Map.of("padding", "true", "truncation", "true", "maxLength", "128"));
}
public long[] encode(String text) {
return tokenizer.encode(text).getIds();
}
public String decode(long[] ids) {
return tokenizer.decode(ids);
}
public int countTokens(String text) {
return tokenizer.encode(text).getTokens().length;
}
}
- O construtor inicializa o tokenizador apenas uma vez quando o bean é criado, e Spring gerencia o ciclo de vida como singleton.
countTokens: útil para verificar limites de comprimento de entrada antes de chamar o modelo (por exemplo, descrição de pedido não deve exceder 512 tokens).
Uso em outro serviço:
@RestController
@RequestMapping("/tokens")
public class TokenController {
private final TokenizerService tokenizerService;
public TokenController(TokenizerService tokenizerService) {
this.tokenizerService = tokenizerService;
}
@GetMapping("/count")
public int count(@RequestParam String text) {
return tokenizerService.countTokens(text);
}
}
Chamar GET /tokens/count?text=Pedido de serviço processa pagamento retornará o número de tokens que o tokenizador BERT cria para essa sentença.
9. Escrever testes
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class TokenizerServiceTest {
@Test
void shouldEncodeAndDecodeRoundTrip() throws Exception {
TokenizerService service = new TokenizerService();
String original = "pagamento falhou";
long[] ids = service.encode(original);
String decoded = service.decode(ids);
assertThat(ids).isNotEmpty();
assertThat(decoded).contains("pagamento").contains("falhou");
}
@Test
void shouldCountMoreTokensThanWordsForSubwordSplit() throws Exception {
TokenizerService service = new TokenizerService();
int tokenCount = service.countTokens("circunstâncias inacreditáveis");
// "inacreditáveis" geralmente é dividida em múltiplos subwords
assertThat(tokenCount).isGreaterThan(2);
}
}
- Teste 1 verifica se a codificação → decodificação round-trip preserva o conteúdo original (pode perder maiúsculas/acentos devido ao tokenizador
uncased). - Teste 2 confirma o comportamento de subword: palavras longas e incomuns são divididas em mais tokens do que o número contado por espaços em branco.
10. Erros comuns e quando não usar
- Misturar tokenizadores entre diferentes modelos: o tokenizador de
bert-base-uncasednão é compatível comgpt2. Sempre carregue o tokenizador com o mesmo nome do modelo que você usa para inferência. - Não calcular attention mask com padding: resulta em saída incorreta do modelo, mas sem exceção — muito difícil de debugar. Sempre passe
attentionMaskjunto cominputIdspara o modelo. - Carregar tokenizador a cada requisição: desperdiça tempo e I/O desnecessários. Sempre declare um bean singleton como
TokenizerServiceacima. - Sem rede no ambiente de produção: a primeira vez que carrega o tokenizador requer internet para baixar arquivo do Hugging Face Hub. Em ambientes offline, você precisa baixar
tokenizer.jsonantecipadamente e carregá-lo de um caminho local usandoHuggingFaceTokenizer.newInstance(Path.of(...)). - Contar tokens para modelo que não é Hugging Face (por exemplo, GPT-4 via OpenAI API): o tokenizador BPE do GPT é diferente do tokenizador Hugging Face padrão. Neste caso, use a biblioteca de contagem de tokens específica do provedor, não
HuggingFaceTokenizer. - Texto em vietnamita com marcas diacríticas: alguns tokenizadores treinados principalmente em inglês podem dividir texto vietnamita de forma não ideal (muito subword estranho). Se processar vietnamita é prioritário, escolha um modelo/tokenizador fine-tuned para vietnamita (por exemplo,
vinai/phobert-base) em vez de BERT original.
11. Conclusão
O tokenizador é um passo fundamental que determina como o modelo “vê” o texto — compreender os mecanismos de BPE/WordPiece/SentencePiece ajuda a evitar erros difíceis de debugar como desajustes de vocabulário ou máscara de atenção incorreta. Com a biblioteca DJL, você pode codificar/decodificar diretamente em Java sem precisar configurar um servidor Python separado, integrando-se perfeitamente em Spring Boot como um bean comum. Lembre-se: sempre use o tokenizador correto do modelo, cache o tokenizador como singleton, e verifique cuidadosamente padding/truncation antes de passar dados para o modelo.