Qwen3-1.7B โ€” Gerador de Questรตes de Matemรกtica (SAEB, offline/mobile)

Fine-tuning do Qwen3-1.7B para gerar questรตes de matemรกtica de mรบltipla escolha no padrรฃo SAEB (avaliaรงรฃo nacional da educaรงรฃo bรกsica brasileira), em JSON estruturado, otimizado para rodar offline em dispositivos mobile via llama.cpp/GGUF.

Arquivos neste repositรณrio

Caminho Conteรบdo
lora/ Adaptadores LoRA (nรฃo mesclados) โ€” para re-mesclar ou re-quantizar com outro mรฉtodo
gguf/ Modelo mesclado e quantizado em Q4_K_M (~1.1GB), pronto para llama.cpp
data/ train.jsonl (273 reais + 244 exemplos sintรฉticos, 396 questรตes) e val.jsonl (30 reais) usados no fine-tuning
eval_report.json Mรฉtricas de qualidade estrutural, perplexity e velocidade (ver abaixo)

Por que este modelo

A equipe jรก havia escolhido o Qwen3-1.7B como base para geraรงรฃo offline de questรตes no app mobile, mas o modelo base รฉ lento (gera blocos de "thinking" longos) e frequentemente falha em produzir um formato utilizรกvel. Este fine-tuning ataca as duas causas:

  1. Formato/qualidade โ€” SFT com QLoRA sobre ~300 questรตes reais do banco SAEB (mais dados sintรฉticos de aritmรฉtica, com resposta garantida por computaรงรฃo), ensinando o modelo a produzir consistentemente o contrato JSON exigido pelo app mobile: {"questoes": [...]} com enunciado, 5 alternativas (Aโ€“E), resoluรงรฃo passo a passo, resposta correta (letra) e dificuldade (EASY/MEDIUM/HARD).
  2. Velocidade โ€” treino e inferรชncia em modo non-thinking (enable_thinking=False), eliminando a cadeia de pensamento solta, com saรญda curta (~150โ€“300 tokens) e exportaรงรฃo para GGUF Q4_K_M, o formato de fato para LLMs offline em Android/iOS.

Dados de treinamento

Extraรญdos de um banco SQLite com 553 itens de avaliaรงรฃo (SAEB), filtrados para:

  • disciplina = Matemรกtica (exclui 19 registros de outras รกreas/nulos)
  • questรตes 100% textuais โ€” excluรญdas 230 questรตes com imagem no enunciado ou nas alternativas, para nรฃo ensinar o modelo a referenciar figuras inexistentes
  • sem alternativas duplicadas (exclui 1 item em que duas alternativas tinham o mesmo texto, tornando o gabarito ambรญguo)

Resultado: 303 questรตes reais vรกlidas, split 90/10 (273 treino / 30 validaรงรฃo) estratificado por (ano escolar, dificuldade). Cobertura: anos 2ยบ/5ยบ/9ยบ, dificuldades Fรกcil/Moderado/Difรญcil, 27 habilidades (descritores) distintas. data/val.jsonl contรฉm sรณ questรตes reais โ€” a validaรงรฃo sempre mede o modelo no que ele de fato vai enfrentar em produรงรฃo.

Aumentaรงรฃo sintรฉtica (generate_synthetic.py): as ~300 questรตes reais ensinam formato/estilo, mas sรฃo poucas para o modelo generalizar aritmรฉtica. O treino atual soma 244 exemplos sintรฉticos (396 questรตes โ€” parte dos exemplos agrupa 2โ€“5 questรตes num รบnico "questoes": [...], ensinando o modelo a responder pedidos de lote) de adiรงรฃo, subtraรงรฃo, multiplicaรงรฃo, divisรฃo, porcentagem, potenciaรงรฃo e fraรงรตes (H07โ€“H09) โ€” com a resposta calculada em Python antes de montar a questรฃo (nunca por um LLM, portanto nunca pode estar errada) โ€” usando as mesmas tuplas (ano, habilidade, descriรงรฃo, dificuldade) reais do banco. Dataset de treino final: 517 exemplos de treino (273 reais + 244 sintรฉticos).

Formato do exemplo (chat SFT) โ€” contrato fixo definido pela equipe de integraรงรฃo do app mobile, sem exceรงรฃo:

  • system: instruรงรฃo fixa definindo o papel e o schema JSON de saรญda
  • user: "Gere {quantidade} questรฃo(รตes) de matemรกtica. Ano: {ano}. Habilidade: {habilidade} โ€” {descriรงรฃo}. Dificuldade: {dificuldade}." (o app pode pedir mais de uma questรฃo por chamada; o modelo sempre responde com uma lista, mesmo quando quantidade=1)
  • assistant: JSON no formato {"questoes": [{"enunciado", "alternativas" (Aโ€“E), "resolucao_passo_a_passo", "resposta_correta", "difficulty"}, ...]}. Internamente o modelo รฉ treinado a emitir resolucao_passo_a_passo antes de resposta_correta โ€” como a geraรงรฃo รฉ token a token, "mostrar o trabalho" antes de se comprometer com a letra final ajuda o modelo a gerar uma resposta consistente com a conta (o modelo roda em modo non-thinking, sem <think>, entรฃo essa รฉ a รบnica รขncora de raciocรญnio disponรญvel). JSON nรฃo garante ordem de chaves para quem consome por nome, entรฃo isso nรฃo afeta o contrato, sรณ a qualidade da geraรงรฃo.

Mรฉtodo de treinamento

QLoRA (Dettmers et al., 2023) via Unsloth + TRL SFTTrainer: modelo base congelado em 4-bit NF4, apenas os adaptadores LoRA sรฃo treinados โ€” viรกvel em GPUs com pouca VRAM (treinado numa RTX 3060 Laptop, 6GB).

Hiperparรขmetro Valor
Modelo base unsloth/Qwen3-1.7B (unsloth/qwen3-1.7b-unsloth-bnb-4bit)
max_seq_length 1024
LoRA rank (r) / alpha 16 / 32
LoRA dropout 0.0
Target modules q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj
Batch efetivo 2 ร— 8 grad. accumulation = 16
Learning rate 2e-4, cosine, warmup 5%
ร‰pocas atรฉ 3, com early stopping (patience=2) por eval_loss
Otimizador adamw_8bit, weight decay 0.01
Precisรฃo bf16
Loss apenas nos tokens de resposta do assistant (train on completions only)
Seed 42

Framework versions: TRL 0.24.0 ยท Transformers 5.5.0 ยท PyTorch 2.11.0.

Resultados de avaliaรงรฃo

Avaliado sobre as 30 questรตes de validaรงรฃo (reais, nรฃo vistas no treino), geraรงรฃo real com temperature=0.7, top_p=0.8 (recomendado pela Qwen para modo non-thinking), apรณs o retreino com o contrato de schema atual (517 exemplos de treino: 273 reais + 244 sintรฉticos):

Mรฉtrica estrutural Resultado
JSON vรกlido 100%
Wrapper {"questoes": [...]} vรกlido 100%
Schema completo (todas as chaves) 100%
resposta_correta vรกlida (Aโ€“E) 100%
5 alternativas distintas 90,0%
difficulty vรกlida (EASY/MEDIUM/HARD) 100%
Menรงรตes indevidas a "figura/imagem" 13,3%
Consistรชncia resposta_correta โ†” resolucao_passo_a_passo* 72,7% (11/30 amostras verificรกveis)
Mรฉtrica de linguagem Resultado
Perplexity (resposta de referรชncia) 1,772
Velocidade (GPU, dev) Resultado
Latรชncia mรฉdia 5,09 s
Latรชncia p95 6,82 s
Tokens/s de geraรงรฃo 30,3
Tokens de saรญda (mรฉdia) 152,7

* Consistรชncia resposta_corretaโ†”resolucao_passo_a_passo: checagem determinรญstica (schema_utils.check_consistency, sem LLM) que extrai uma expressรฃo aritmรฉtica simples "a op b = r" do texto de resolucao_passo_a_passo e confere se bate com o valor da alternativa apontada por resposta_correta. Medida sรณ sobre as N amostras (aqui, 11 de 30) em que essa expressรฃo existe de forma reconhecรญvel no texto โ€” questรตes com raciocรญnio verbal (sem "a op b = r" explรญcito) ou resposta textual (fraรงรฃo/porcentagem) nรฃo entram na conta. Este teto รฉ conhecido e aceito: uma versรฃo anterior deste pipeline tinha um campo resposta dedicado ao valor da resposta, que elevava esta mรฉtrica a 100% por comparaรงรฃo exata; ele foi removido para seguir o contrato de schema exigido pela integraรงรฃo com o app mobile (ver DOCUMENTACAO_CIENTIFICA.md, Seรงรฃo 3.2), e a cobertura de verificaรงรฃo voltou a depender de regex sobre texto livre. Mitigado, nรฃo eliminado, pelo pipeline de best-of-N + correรงรฃo determinรญstica em test_model.py.

Velocidade โ€” o nรบmero relevante para mobile รฉ o do GGUF quantizado, nรฃo o do modelo em 4-bit via bitsandbytes+HF generate() (caminho nรฃo otimizado, usado sรณ durante o desenvolvimento). Benchmark real do .gguf com llama-bench (CPU, proxy de mobile โ€” sem aceleraรงรฃo de GPU, mรกquina ociosa):

Config Prompt processing Geraรงรฃo
4 threads 123 tok/s 29,7 tok/s
16 threads 116 tok/s 17,1 tok/s (contenรงรฃo de banda de memรณria)

Geraรงรฃo รฉ limitada por banda de memรณria, nรฃo por nรบcleos โ€” por isso menos threads foi mais rรกpido. O nรบmero real em um SoC de celular deve ser medido no dispositivo alvo, mas ~30 tok/s de CPU aqui jรก indica viabilidade para uso offline (โ‰ˆ153 tokens de saรญda mรฉdios โ†’ poucos segundos por questรฃo).

Validaรงรฃo do artefato real (GGUF): test_model.py --batch foi rodado antes desta migraรงรฃo de contrato โ€” o relatรณrio anterior media gabarito_valido/justificativas_distintas (schema antigo) e nรฃo reflete o .gguf atual. Rode python src/test_model.py --batch para gerar outputs/eval_report_gguf.json com as mรฉtricas do contrato atual (wrapper_valido, resposta_valida, difficulty_valida, consistencia_resposta_correta) sobre o artefato exportado hoje.

Como usar (adaptadores LoRA, com Unsloth)

from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="<este-repo>/lora",
    max_seq_length=1024,
    load_in_4bit=True,
)
FastLanguageModel.for_inference(model)

messages = [
    {"role": "system", "content": "Vocรช รฉ um gerador de questรตes de matemรกtica no padrรฃo SAEB..."},
    {"role": "user", "content": "Gere 1 questรฃo(รตes) de matemรกtica. Ano: 5ยบ ano. Habilidade: H08 โ€” ... . Dificuldade: Fรกcil."},
]
inputs = tokenizer.apply_chat_template(
    messages, tokenize=True, add_generation_prompt=True,
    enable_thinking=False, return_tensors="pt",
).to(model.device)
output = model.generate(inputs, max_new_tokens=512, temperature=0.7, top_p=0.8)
print(tokenizer.decode(output[0, inputs.shape[1]:], skip_special_tokens=True))

Como usar (GGUF, llama.cpp โ€” uso mobile/offline)

llama-cli -m gguf/qwen3-1.7b.Q4_K_M.gguf --temp 0.7 --top-p 0.8 \
  --grammar-file grammars/questao.gbnf \
  -p "Gere 1 questรฃo(รตes) de matemรกtica. Ano: 9ยบ ano. Habilidade: H17 โ€” ... . Dificuldade: Moderado."

Recomendado usar a grammar GBNF (grammars/questao.gbnf) na inferรชncia para forรงar o contrato exato โ€” wrapper {"questoes": [...]}, 5 alternativas (Aโ€“E), resposta_correta em {A,B,C,D,E}, difficulty em {EASY,MEDIUM,HARD} โ€” cobrindo os casos em que o modelo ainda erra o formato.

Limitaรงรตes

  • resposta_correta ocasionalmente inconsistente com resolucao_passo_a_passo: em alguns casos a conta na resoluรงรฃo estรก certa, mas a letra nรฃo bate com ela โ€” sintoma esperado de um modelo pequeno (1.7B) sem chain-of-thought explรญcito, gerando de forma autorregressiva. Mitigado (nรฃo eliminado) por: (1) treinar o modelo a emitir a resoluรงรฃo antes de se comprometer com a letra, e (2) generate_synthetic.py, que soma exemplos de aritmรฉtica (e fraรงรตes) com resposta garantida por computaรงรฃo. Ver mรฉtrica "Consistรชncia resposta_corretaโ†”resolucao_passo_a_passo" acima โ€” hoje limitada a ~73% porque o contrato do app nรฃo permite um campo dedicado ao valor da resposta (sรณ a letra), entรฃo a checagem depende de regex sobre texto livre. Para produรงรฃo, recomenda-se rodar a checagem determinรญstica de schema_utils.check_consistency() (e o best-of-N de test_model.py) como validaรงรฃo pรณs-geraรงรฃo antes de exibir a questรฃo ao usuรกrio.
  • Dataset real pequeno (~300 exemplos): ensina bem formato e estilo pedagรณgico, mas sozinho nรฃo รฉ suficiente para ampliar conhecimento matemรกtico alรฉm do que jรก estava no modelo base โ€” daรญ a augmentaรงรฃo sintรฉtica de aritmรฉtica e fraรงรตes.
  • Nรฃo gera questรตes com figuras/grรกficos (removidas do treino de propรณsito); ainda assim ~13% das saรญdas de validaรงรฃo mencionam "figura/imagem" mesmo sem terem sido pedidas โ€” acompanhar essa mรฉtrica nas prรณximas iteraรงรตes.
  • A 5ยช alternativa (E) das questรตes reais do banco รฉ sempre um distrator fixo ("Nenhuma das alternativas anteriores"), jรก que o banco original sรณ tem Aโ€“D โ€” sรณ as questรตes sintรฉticas tรชm 5 distratores pedagogicamente distintos.
  • Leve viรฉs na distribuiรงรฃo das respostas corretas (letras C/D sub-representadas), herdado do prรณprio banco de dados original.
  • Caminho de evoluรงรฃo recomendado: destilaรงรฃo de dados sintรฉticos mais ampla โ€” gerar questรตes contextualizadas (nรฃo sรณ cรกlculo puro) com um modelo maior, filtrar por qualidade/consistรชncia e reincorporar ao dataset de treino.

Citaรงรฃo / proveniรชncia dos dados

Questรตes extraรญdas de um banco de itens SAEB de uso interno do projeto. Cรณdigo de treinamento e extraรงรฃo disponรญvel no repositรณrio GitHub linkado acima.

Downloads last month
90
GGUF
Model size
2B params
Architecture
qwen3
Hardware compatibility
Log In to add your hardware

4-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for eltonsarmanho/qwen3-1.7b-questoes-matematica

Finetuned
Qwen/Qwen3-1.7B
Adapter
(22)
this model