Avançado

O ecossistema Hugging Face Transformers

Entre os pesos no Hub e uma resposta existem quatro peças móveis — fixação de revisão, carregamento de safetensors, o chat template e generate() — e só uma delas é o modelo.

Atualizada em

01 · Conceito

Conceito

Um colega relata que o Qwen3.8-27B “ignora o botão de thinking” — ele desliga, o modelo raciocina mesmo assim, e a flag parece decorativa. Outro relata que o mesmo script dava respostas diferentes no mês passado e hoje dá outras, sem nenhuma mudança de código. Os dois bugs são reais, nenhum está no modelo, e ambos vivem na fina camada de encanamento entre os pesos no Hub e o texto na tela. Essa camada tem quatro partes: quais bytes você baixou, como eles foram carregados, como sua conversa virou uma string e como os tokens foram amostrados. A lição 8.1 deu o runtime; esta lição dá tudo que está enrolado em volta dele.

Comece pela procedência. Um repositório do Hub como Qwen/Qwen3.8-27B é um repositório git com arquivos grandes, e um identificador de repositório sozinho nomeia um branch, não um snapshot. Carregar sem fixar uma revisão significa “o que quer que main aponte quando o cache de download falhar” — que é por que o mesmo script deriva ao longo dos meses conforme o fornecedor publica uma correção de tokenizer ou um ajuste de config. Fixe um hash de commit, registre-o ao lado dos seus resultados de avaliação e trate o diretório do modelo como um artefato com versão, exatamente como você faria com uma imagem de contêiner.

Depois o formato. O Qwen3.8-27B é distribuído em safetensors bf16, e o safetensors é deliberadamente sem graça: um header JSON listando cada tensor com seu dtype, shape e faixa de bytes, seguido de um único blob contíguo. Carregar é um mapeamento de memória mais fatiamento, então nada é desserializado em objetos Python e nenhum código do arquivo chega a rodar. Os 54 GB de pesos em bf16 são divididos em shards com um arquivo de índice mapeando nomes de tensor para shards, que é como a lição 4.14 conseguiu abrir pesos reais e encontrar model.layers.31.mlp.gate_proj.weight sem carregar os outros cinquenta gigabytes. As consequências práticas são três: o carregamento é rápido porque as páginas chegam sob demanda, um checkpoint malicioso não pode executar no load como um pickle pode, e os nomes de tensor são uma interface pública estável da qual scripts de conversão e adaptadores LoRA dependem.

Agora a parte que causa o bug relatado. O Qwen3.8-27B é um modelo de chat, e modelos de chat são treinados sobre um layout de string muito específico — marcadores de papel, fronteiras de turno e os special tokens que delimitam um bloco de thinking. Esse layout não está embutido nos pesos e não está na config do modelo. É um template Jinja distribuído junto com o tokenizer, e apply_chat_template é o que o renderiza. Você entrega uma lista de dicionários com papéis e conteúdos, ele devolve exatamente a string que o modelo foi treinado a continuar, e tokenizar essa string dá os ids para o forward pass. O modelo nunca vê um papel. Ele vê tokens.

É aí que enable_thinking mora: é uma variável consumida pelo template, não um parâmetro do modelo e não um argumento do loop de geração. Defini-la como false faz o template renderizar um prompt cujo andaime suprime o bloco de thinking; defini-la como true (o padrão) renderiza a versão que convida a um. Pesos idênticos, forward pass idêntico, tokens de entrada diferentes. Então a sequência é: montar as mensagens, chamar o template com add_generation_prompt=True e sua escolha de thinking, tokenizar e desempacotar o encoding devolvido em generate().

inputs = tok.apply_chat_template(
    messages,
    add_generation_prompt=True,
    enable_thinking=False,
    return_dict=True,
    return_tensors="pt",
).to(model.device)
out = model.generate(
    **inputs,
    do_sample=True,
    temperature=0.7,
    top_p=0.80,
    top_k=20,
    min_p=0.0,
    repetition_penalty=1.0,
    max_new_tokens=256,
)

Este trecho supõe que tok e um model em um único device foram carregados da mesma revisão fixada e que messages já está definido. No Transformers v5, apply_chat_template(..., return_tensors="pt") devolve um BatchEncoding, então passá-lo como argumento posicional ids falha; **inputs fornece tanto input_ids quanto attention_mask. do_sample=True também é necessário para temperature, top-p, top-k e min-p afetarem o decoding. O limite de comprimento fica explícito para que um exemplo copiado tenha uma fronteira finita.

Como o template é um arquivo, e não uma propriedade dos pesos, ele também é algo que as pessoas redistribuem e substituem. Existem repositórios que não contêm peso nenhum — só um chat_template.jinja e uma nota sobre o que ele muda — e, como o GGUF carrega o template no seu header de metadados e conversões MLX o mantêm ao lado do tokenizer, um único arquivo desses pode ser apontado para o mesmo modelo sob runtimes diferentes. O que isso compra é real, mas limitado: um template pode mudar o enquadramento do system, o scaffolding em torno de um bloco de thinking e como as definições de tools são dispostas, de modo que pode encurtar respostas de forma mensurável ou mudar o formato delas. Ele não muda o que o modelo sabe. Trate uma economia de tokens alegada como a lição 0.8 ensina a tratar qualquer número de benchmark — pergunte qual modelo, qual harness, quantas amostras e contra qual template de base — e tenha em mente que trocar o template afasta o modelo do layout em que ele foi ajustado, o que é uma troca, não um ganho de graça.

O tool calling anda pelo mesmo mecanismo, e é por isso que ele cabe aqui em vez de numa lição própria. Você passa as definições de tools ao apply_chat_template junto com as mensagens; o template as renderiza no layout exato de special tokens que o modelo foi treinado para responder; o modelo emite uma chamada estruturada como tokens comuns; e o seu harness converte esse trecho de volta em nome de função e argumentos, executa e anexa o resultado como mais uma mensagem. Cada um desses passos é texto. Nada no forward pass sabe que existe uma tool. Quando o tool calling se comporta mal, o bug quase sempre está na renderização ou no parsing, não no modelo — e a lição 8.5 cobre a metade complementar: restringir o decode para que a chamada emitida tenha parsing garantido.

O sampling é a segunda falha silenciosa. O preset instruct completo do model card atual é temperature 0.7, top_p 0.80, top_k 20, min_p 0, presence penalty 1.5 e repetition penalty 1.0. O thinking mode usa temperature 1.0, top_p 0.95, top_k 20, min_p 0, presence penalty 0 e repetition penalty 1.0. A interface bruta de generate() do Transformers na revisão citada suporta os campos mostrados no trecho, mas não tem argumento de presence penalty; portanto, o trecho é uma implementação parcial e declarada do preset do card. Defina todo campo suportado, registre os não suportados e logue a requisição efetiva — não presuma que padrões omitidos casam com o card.

O thinking tem três controles independentes. enable_thinking decide se existe um bloco de raciocínio. reasoning_effort controla a profundidade e hoje assume xhigh por padrão no card oficial; os rótulos são semântica de API e template, então inspecione o template efetivo antes de comparar runtimes. preserve_thinking hoje vem true e retém blocos de raciocínio históricos. Isso pode ajudar um agente, mas também faz raciocínio antigo consumir tokens de prompt em cada turno. Meça tokens e tempo do job inteiro e decida deliberadamente se esse histórico pertence ao contexto.

Nada disso é um serving stack. As requisições rodam uma depois da outra, memória é alocada por chamada, não há paginação nem continuous batching, e um segundo usuário concorrente espera. Isso é uma virtude para trabalho de corretude e uma desqualificação para produção, e é por isso que a lição 8.4 retoma o fio com o vLLM. O que você leva adiante é a disciplina: fixe a revisão, carregue safetensors, renderize o template, defina o preset e saiba qual dos quatro está mentindo antes de culpar o modelo.

02 · Analogia

Analogia

Uma sala de concertos não entrega o compositor. Ela entrega uma edição impressa da partitura, um bibliotecário que distribui a revisão certa, um maestro que decide andamento e dinâmica e um contrarregra que diz quando a peça acaba. A culpa por uma execução ruim costuma cair sobre o compositor e costuma pertencer a um dos outros três. O Hub, o safetensors, o chat template e o loop de geração são esses três papéis em torno do Qwen3.8-27B.

03 · Explique de volta

Explique de volta

Trace uma requisição de chat através do stack Transformers e explique precisamente onde enable_thinking faz efeito, e por que chamá-lo de configuração do modelo está errado.

Mínimo: 80 caracteres e 15 palavras. Seu texto fica somente neste navegador.

Aguardando sua explicação.

Comparar com uma resposta-modelo

Uma lista de mensagens com papel e conteúdo vai para o apply_chat_template do tokenizer, que renderiza o template Jinja do modelo — guardado com o tokenizer, não com os pesos — em uma única string carregando exatamente os special tokens com que o modelo foi treinado, e então a tokeniza. O Transformers atual devolve um BatchEncoding com input_ids e attention_mask, e generate() recebe esse mapeamento, roda o forward pass repetidamente, faz sampling quando do_sample=True com os parâmetros de decoding fornecidos, mantém o cache e para em um stop token ou no limite de comprimento. enable_thinking é um argumento de apply_chat_template: ele muda qual andaime o template emite e, portanto, quais tokens o modelo recebe. Os pesos, a config e o forward pass são byte a byte idênticos nos dois casos. Se você montar a string do prompt na mão, ou passar a flag para generate() em vez de para o template, ela não faz absolutamente nada e você vai concluir que o modelo a ignora.

04 · Teste seu entendimento

Teste seu entendimento

01Por que carregar safetensors não executa código arbitrário, ao contrário de um checkpoint em pickle?
Resposta e explicação

O arquivo é um header JSON com nomes, dtypes, shapes e offsets de bytes seguido de um blob plano de tensors, então carregar é mapear memória e fatiar — Não há grafo de objetos a reconstruir; o loader lê offsets e mapeia bytes, o que é ao mesmo tempo mais seguro e mais rápido que despicklar.

02Pela lição 8.1, o que você deve comparar quando uma resposta do Transformers e a de um serving engine divergem?
Resposta e explicação

O primeiro token divergente sob decoding guloso, e então se o tokenizer, o chat template ou a configuração de sampling explica a diferença — não igualdade bit a bit de floats — Reduções em bf16 variam com a ordem de acumulação, então o PyTorch eager define corretude no nível de sequências de tokens e distribuições, não de floats idênticos.

03Qual preset de sampling o model card dá para o thinking mode?
Resposta e explicação

temperature 1.0, top_p 0.95, top_k 20 — O thinking mode é o padrão e pede temperature 1.0 com top_p 0.95 e top_k 20; o preset instruct é o mais apertado 0.7 / 0.80 / 20.

Conclua o teach-back e acerte o quiz para finalizar a aula.

◎ · Marcador de evidência

Fontes

  1. Hugging Face (2026). Hugging Face Transformers Documentation.
  2. Hugging Face (2026). Transformers v5 Migration Guide — apply_chat_template returns BatchEncoding.
  3. Hugging Face (2026). safetensors Documentation.
  4. Qwen Team (2026). Qwen3.8-27B Model Card.