Qwen3-1.7B SAEB Math Question Generator (offline / on-device) — v3
English
Summary
This model is a QLoRA fine-tune of Qwen3-1.7B that writes multiple-choice mathematics questions in the style of SAEB, Brazil's national assessment of basic education. Each output is a structured JSON object ready for a mobile app, and the model is packaged as a quantized GGUF so it can run fully offline on phones and tablets through llama.cpp.
This is the v3 release. It replaces the L2-2026-09 cycle with a much
larger, agent-curated item bank (2,717 training examples across all 6
grades, up from 963 covering 6 grades with thin coverage), and it was
promoted against the original baseline_v1 GGUF under a multi-seed
promotion gate (90 paired samples across 3 seed rounds, not the previous
single 30-sample pass) — see Promotion gates below for why that change was
made and what it changes.
| Code and data | https://github.com/eltonsarmanho/TreinamentoModeloQuestoes |
| Base model | unsloth/Qwen3-1.7B (4-bit NF4) |
| Method | QLoRA, supervised fine-tuning with TRL SFTTrainer on Unsloth |
| Deployment format | GGUF, Q4_K_M quantization (~1.1 GB), llama.cpp |
| Output language | Brazilian Portuguese |
| Data cycle | v3 (déficit-closing + agent pipeline), promoted on 2026-10-05 via the multiseed gate (G1–G9, 90 paired samples) |
| Artifact hash | sha256 3a7682595ed2d278…; baseline_v1 kept (sha256 b461b10fbf23622d…) for rollback |
Repository contents
| Path | Contents |
|---|---|
lora/ | Unmerged LoRA adapters, for re-merging or re-quantizing with another method |
gguf/ | Merged model quantized to Q4_K_M, ready for llama.cpp |
data/ | train.jsonl (2,717 chat-SFT examples, 2,759 questions) and val.jsonl (30 real items, frozen, identical to every previous cycle) |
eval_report.json | Structural quality and speed metrics (seed round 0 of the multiseed gate) |
VEREDITO_v3_multiseed.json (see GitHub repo, outputs/relatorios/) | Full multiseed promotion gate verdict |
Motivation and design
Unchanged from the base design: non-thinking mode, a fixed JSON contract
enforced by a GBNF grammar, and a deterministic post-generation checker
(generate_validated) that regenerates on failure. What changed in v3 is
how the training bank itself is built and audited — see below.
Training data
Cycle v3: agent-curated bank, déficit-closing, dual review
The L2-2026-09 bank (1,039 items, 6 grades, uneven per-skill coverage) went
through a second pipeline built to (a) audit every existing item for
mathematical correctness, and (b) close the per-skill, per-subtopic and
per-difficulty gaps against an explicit target (≥30 items/skill, ≥3/subtopic,
balanced difficulty).
Agent pipeline (src/agentes_questoes.py, src/auditar_base.py,
src/injetar_questoes.py, src/arbitro_gemini.py):
- Auditor — every existing item re-judged by a blind validator +
reviewer pair (
sabia-4-thinking). Items a reviewer would remove for a confirmed math error go to a Gemini arbiter (gemini-2.5-flash) before any automatic removal — a single model's mistake never removes an item unseconded. - Injector — new candidates generated per skill with a documented deficit, judged by the same blind validator + reviewer, with a Gemini second reviewer added after the first injection round (both must approve; Gemini's prompt explicitly screens out "requires a figure the mobile app can't show" false positives). The Gemini call budget was bounded by a hard spend cap; on exhaustion or 3 consecutive API failures the pipeline degrades gracefully to the two local judges rather than stalling.
- Human decisions layer (
Doc/decisoes_humanas.json, 36 recorded decisions) — takes precedence over every automatic rule: confirmed math errors removed, easy-but-correct items relabeled instead of dropped, an automatic difficulty-downgrade rule suspended after measuring reviewer agreement (κ = 0.087, too close to chance to trust), and specific ambiguous items removed by inspection. - A symbolic geometry verifier (
src/verificador_geometria.py) checks grade-9 classification questions (triangles/quadrilaterals) against the stated premises. It ships in shadow mode: it logs a verdict (V/F/inconclusive) on every sample but does not yet block or regenerate anything, because it still abstains on about 40% of grade-9 geometry items. Promoting it to active mode is gated on a blind human audit of a fresh sample.
L2-2026-09 | v3 | |
|---|---|---|
| Training examples | 963 | 2,717 |
| Questions | ~1,100 | 2,759 |
| Grades covered | 1–5, 9 (uneven) | 1–5, 9 (explicit ≥30/skill target; 34/79 skills still below target, documented) |
| Skills (grade × code) | — | 79 |
| Provenance (examples) | real + synthetic | real 693 (25.5%) + synthetic 59 + distilled/injected 1,965 (72.3%) |
| Near-duplicates within a skill | not tracked | 29 (1.1%), flagged by src/avaliar_diversidade.py |
| Answer-key balance (A/B/C/D/E) | 46.7% most-frequent letter (frozen set) | 24.9/23.3/22.4/18.1/11.2% bank-wide; 30.0% most-frequent letter on the frozen set (gate G6) |
data/val.jsonl stays byte-identical across all three cycles
(L2-2026-09 and v3): no new item was ever added to it, so every
promotion decision compares the two models on exactly the same 30 questions.
Full breakdown by grade, skill and remaining deficit:
Doc/RELATORIO_BASE_v3.md in the GitHub repository (generated by
src/relatorio_base.py, not hand-edited).
Example format (chat SFT, unchanged): system / user / assistant as in
L2-2026-09 — see the GitHub repo for the exact schema.
Training procedure
Unchanged QLoRA setup (same code, same hyperparameters as L2-2026-09) —
only the dataset changed, which keeps the comparison to baseline_v1
interpretable as a data effect, not a recipe effect.
| Hyperparameter | Value |
|---|---|
| Base model | unsloth/Qwen3-1.7B (unsloth/qwen3-1.7b-unsloth-bnb-4bit) |
max_seq_length | 1024 |
| LoRA rank / alpha | 16 / 32 |
| Target modules | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
| Effective batch size | 2 × 8 gradient accumulation = 16 |
| Learning rate | 2e-4, cosine schedule, 5% warmup |
| Epochs | 3 (early stopping on eval_loss, patience 2) |
| Optimizer | adamw_8bit, weight decay 0.01 |
| Precision | bf16 |
| Loss | assistant tokens only (train on completions) |
| Seed | 42 |
eval_loss per epoch: 0.5899 → 0.5614 (best, epoch 2) → 0.5631.
Final train_loss: 0.4691.
Evaluation
Promotion gates (src/promover_checkpoint.py --perfil multiseed)
The L2-2026-09 gate ran one pass over the 30-item frozen set. A gate
decision reproduced from the v3 release reproduced exactly one item
(MT9027MH06MT, grade-9 H06) failing both G2 (irrecoverable failure) and G3
(answer–solution consistency) — on 30 samples, a single item is worth
10 percentage points of G3. Revised 2026-10-05, registered as a revision
made after a reproved verdict (same practice already used for G4 in
2026-09-16 and G11 in 2026-09-30): the gate now requires ≥3 independent
seed rounds (90 paired samples, same prompts, same pairing-by-item,
different generation seeds) and decides G2/G3 by paired McNemar plus an
absolute 5% failure ceiling, instead of one pass over 30 items. Every other
gate (G1, G4–G8) still has to pass in every round — the revision loosens
nothing else.
| Gate | Criterion | Result (90 paired samples, 3 seed rounds) |
|---|---|---|
| G1 Structural contract | ≥ 99% and ≥ baseline, all rounds | passed, all 3 rounds |
| G2* Failures after best-of-N | no significant increase (McNemar) and ≤ 5% absolute | 2/90 → 2/90 (2.2%); 2 worse, 2 better, p = 1.000 |
| G3* Consistency | no significant decrease (McNemar) | 100% (n=31) → 94.3% (n=35); 2 worse, 0 better, p = 0.500 |
| G4 Visual dependence | no significant increase, all rounds | passed, all 3 rounds |
| G5 Difficulty adherence | ≥ baseline − 5pp, all rounds | passed, all 3 rounds |
| G6 Answer-key bias | ≤ baseline + 5pp, all rounds | passed, all 3 rounds |
| G7 Forgetting (grades 5, 9) | no structural regression, all rounds | passed, all 3 rounds |
| G8 Speed | ≥ baseline − 10%, all rounds | passed, all 3 rounds |
| G9 Perplexity (informational) | — | not measured |
Decision: PROMOVIDO (promoted). Gains: verifiable-by-machine samples
31 → 35 (out of 90); answer-key bias (most frequent letter) 43.8% → 30.0%.
Full verdict, per-round relatório JSON and the McNemar-pair detail live in
outputs/relatorios/VEREDITO_v3_multiseed.json in the GitHub repository.
How to use
LoRA adapters with Unsloth
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="<this-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: 9º ano. Habilidade: H06 — Cálculo com números reais: adição, subtração, multiplicação e divisão.. Dificuldade: Moderado."},
]
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))
GGUF with llama.cpp (offline / mobile)
Basic usage:
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: 5º ano. Habilidade: H08 — ... . Dificuldade: Difícil."
Always combine the GBNF grammar with generate_validated()
(tests/test_model.py), which checks structure, consistency and visual
dependence before returning a question and regenerates when needed.
Optimized for speed (mobile apps):
To achieve the ~51% latency reduction measured in production (15.2 s → 7.4 s per question on an i7-11800H):
-
Run a persistent llama-server instead of spawning one
llama-cliper question:llama-server -m gguf/qwen3-1.7b.Q4_K_M.gguf --jinja \ -c 2048 -np 1 -t 8 --port 8080 --host 127.0.0.1-c 2048: reduce context from the model's 40960 default (saves 4.4 GB of KV cache overhead per call).-t 8: use physical CPU cores (8 for an i7; adjust to your device). More threads don't help generation (memory-bound), but prefill speeds up with more cores up to a saturation point.-np 1: one slot, to keep the context undivided. Auto-slots split it, preventing the worst-case prompt from fitting.
-
Enable prompt caching in your API calls (via
/v1/chat/completions):{ "messages": [ {"role": "system", "content": "Você é um gerador de questões..."}, {"role": "user", "content": "Gere 1 questão..."} ], "temperature": 0.7, "top_p": 0.8, "max_tokens": 512, "cache_prompt": true, "grammar": "<gbnf-grammar-text>" }- The system prompt (~300 tokens) is identical across all calls and only needs to be processed once.
-
Keep the model in memory between questions — do not reload it per question.
-
One question per call (set
quantidade=1in the prompt). Tests show 100% success on first attempt with one question, but only 65% with two and 40% with three.
Expected gains (measured on i7, 8 physical cores):
- Latency: 15.2 s → 7.4 s per question (−51%).
- Peak RAM: 5.8 GB → 2.4 GB.
Note: these metrics are from a desktop CPU. Mobile performance will depend on device specs (physical cores, LPDDR memory bandwidth). Measure on your target device before committing to time targets.
Limitations
- Grade-9 math verification is still weak. The symbolic geometry verifier runs in shadow mode (abstains on ~40% of grade-9 classification items) and does not yet block or regenerate anything. Both of the v3 gate's irrecoverable-failure items (seed rounds 0 and 1) were grade-9. Promoting the verifier to active mode is gated on a blind human audit of a fresh sample — not done yet.
- 34 of 79 skills are still below the ≥30-item target after the
déficit-closing round (down from a larger initial gap). The shortfall
concentrates in grades 2, 5 and 9 — see
Doc/RELATORIO_BASE_v3.mdfor the per-skill table. Generation attempts for the remaining skills mostly ran into the same filters (subtopic mismatch, answer-key error) rather than budget exhaustion, so simply spending more API budget on the same skills is unlikely to close them. - 72.3% of training examples are distilled/injected, not drawn from the
original human-written real bank (25.5%). The agent pipeline's dual review
(plus a Gemini second reviewer on new items) is the main quality control;
it has not been validated against a fully independent human audit at
scale — only a sample of automatic removals and human-review flags
(
outputs/agentes/revisao_humana.jsonl) has been spot-checked. - The ~1,234 items accepted in earlier cycles (before the Gemini second reviewer existed) have no Gemini vote — only the local judge pair.
- Calibration of the judge models is partly in-sample (same model family as parts of the generation pipeline).
- Speed numbers come from a desktop CPU with 4 threads; on-device (phone) latency and memory use are not yet measured for this release.
Intended use and ethical considerations
The model supports teachers; it does not replace them. Generated questions should be reviewed by an educator before reaching students, especially for grade 9 content, where automatic verification is weakest. Not designed for high-stakes assessment.
Data provenance
Questions come from the internal SAEB-style bank, a contributor-submitted
batch (L2-2026-09), and the v3 déficit-closing agent pipeline
(generator + blind validator + reviewer, with a Gemini arbiter on
removals and a Gemini second reviewer on new injected items). All pipeline
code, decision logs and the full audit trail are in the GitHub repository
linked above.
Citation
@misc{sarmanho2026qwen3saebv3,
author = {Sarmanho, Elton},
title = {{Qwen3-1.7B SAEB Math Question Generator} (v3):
Agent-Curated, Dual-Reviewed On-Device Generation of
Structured Multiple-Choice Mathematics Questions for
Brazilian Basic Education},
year = {2026},
month = oct,
howpublished = {\url{https://github.com/eltonsarmanho/TreinamentoModeloQuestoes}},
note = {QLoRA fine-tune of Qwen3-1.7B, data cycle v3,
GGUF Q4\_K\_M release, multiseed promotion gate}
}
@inproceedings{dettmers2023qlora,
author = {Dettmers, Tim and Pagnoni, Artidoro and Holtzman, Ari and Zettlemoyer, Luke},
title = {{QLoRA}: Efficient Finetuning of Quantized {LLMs}},
booktitle = {Advances in Neural Information Processing Systems},
volume = {36},
year = {2023}
}
@misc{qwen3technicalreport,
author = {{Qwen Team}},
title = {{Qwen3} Technical Report},
year = {2025},
eprint = {2505.09388},
archivePrefix = {arXiv},
primaryClass = {cs.CL}
}
Português
Resumo
Este modelo é um ajuste fino (QLoRA) do Qwen3-1.7B que elabora questões de matemática de múltipla escolha no padrão do SAEB. Cada saída é um objeto JSON estruturado, pronto para o app mobile, distribuído em GGUF quantizado para rodar totalmente offline via llama.cpp.
Esta é a versão v3. Ela substitui o ciclo L2-2026-09 por um banco
muito maior e curado por agentes (2.717 exemplos de treino em todos os 6
anos, contra 963 com cobertura desigual), promovida contra o GGUF original
baseline_v1 sob um gate de promoção multiseed (90 amostras pareadas em
3 rodadas de seed, não mais uma única passada de 30 amostras) — ver Gates
de promoção abaixo para o porquê da mudança.
| Código e dados | https://github.com/eltonsarmanho/TreinamentoModeloQuestoes |
| Modelo base | unsloth/Qwen3-1.7B (4-bit NF4) |
| Método | QLoRA, ajuste supervisionado com TRL SFTTrainer sobre Unsloth |
| Formato de deploy | GGUF, quantização Q4_K_M (~1,1 GB), llama.cpp |
| Idioma de saída | Português do Brasil |
| Ciclo de dados | v3 (fechamento de déficit + pipeline de agentes), promovido em 05/10/2026 pelo gate multiseed (G1–G9, 90 amostras pareadas) |
| Hash do artefato | sha256 3a7682595ed2d278…; baseline_v1 preservado (sha256 b461b10fbf23622d…) para reversão |
Arquivos neste repositório
| Caminho | Conteúdo |
|---|---|
lora/ | Adaptadores LoRA não mesclados |
gguf/ | Modelo mesclado e quantizado em Q4_K_M, pronto para llama.cpp |
data/ | train.jsonl (2.717 exemplos chat-SFT, 2.759 questões) e val.jsonl (30 itens reais, congelado, idêntico a todos os ciclos anteriores) |
eval_report.json | Métricas estruturais e de velocidade (rodada de seed 0 do gate multiseed) |
Motivação e desenho
Inalterado em relação ao desenho base: modo non-thinking, contrato JSON
fixo forçado por grammar GBNF, e checagem determinística pós-geração
(generate_validated) que regenera em caso de falha. O que mudou na v3 foi
como o próprio banco de treino é construído e auditado — ver abaixo.
Dados de treinamento
Ciclo v3: banco curado por agentes, fechamento de déficit, dupla revisão
O banco do L2-2026-09 (1.039 itens, 6 anos, cobertura desigual por
habilidade) passou por um segundo pipeline construído para (a) auditar
matematicamente cada item existente e (b) fechar as lacunas por habilidade,
subtema e dificuldade contra uma meta explícita (≥30 itens/habilidade,
≥3/subtema, dificuldade equilibrada).
Pipeline de agentes (src/agentes_questoes.py, src/auditar_base.py,
src/injetar_questoes.py, src/arbitro_gemini.py):
- Auditor — cada item existente rejulgado por um par validador +
revisor cego (
sabia-4-thinking). Itens que um revisor removeria por erro matemático confirmado vão a um árbitro Gemini (gemini-2.5-flash) antes de qualquer remoção automática — o erro de um único modelo nunca remove um item sozinho. - Injetor — candidatos novos gerados por habilidade com déficit documentado, julgados pelo mesmo par validador + revisor cego, com um segundo revisor Gemini adicionado após a primeira rodada de injeção (os dois precisam aprovar; o prompt do Gemini filtra explicitamente falsos positivos de "depende de uma figura que o app não mostra"). O orçamento de chamadas ao Gemini teve teto duro de gasto; ao esgotar ou após 3 falhas consecutivas de API, o pipeline degrada graciosamente para os dois juízes locais em vez de travar.
- Camada de decisões humanas (
Doc/decisoes_humanas.json, 36 decisões registradas) — tem precedência sobre toda regra automática: erros matemáticos confirmados removidos, itens fáceis-mas-corretos rerrotulados em vez de descartados, uma regra automática de rebaixamento de dificuldade suspensa após medir a concordância entre revisores (κ = 0,087, perto demais do acaso para confiar), e itens ambíguos específicos removidos por inspeção. - Um verificador simbólico de geometria
(
src/verificador_geometria.py) checa questões de classificação do 9º ano (triângulos/quadriláteros) contra as premissas enunciadas. Roda em modo sombra: registra um veredito (V/F/inconclusivo) em cada amostra, mas ainda não bloqueia nem regenera nada, porque ainda se abstém em cerca de 40% dos itens de geometria do 9º ano. Promovê-lo a modo ativo depende de uma auditoria humana cega de uma amostra nova — ainda não feita.
L2-2026-09 | v3 | |
|---|---|---|
| Exemplos de treino | 963 | 2.717 |
| Questões | ~1.100 | 2.759 |
| Anos cobertos | 1º–5º, 9º (desigual) | 1º–5º, 9º (meta explícita ≥30/habilidade; 34/79 habilidades ainda abaixo, documentado) |
| Habilidades (ano × código) | — | 79 |
| Proveniência (exemplos) | real + sintético | real 693 (25,5%) + sintético 59 + destilado/injetado 1.965 (72,3%) |
| Near-duplicatas na mesma habilidade | não medido | 29 (1,1%), sinalizado por src/avaliar_diversidade.py |
| Equilíbrio do gabarito (A/B/C/D/E) | 46,7% letra mais frequente (conjunto congelado) | 24,9/23,3/22,4/18,1/11,2% no banco inteiro; 30,0% letra mais frequente no conjunto congelado (gate G6) |
O data/val.jsonl permanece idêntico byte a byte em todos os ciclos
(L2-2026-09 e v3): nenhum item novo foi adicionado, então toda decisão
de promoção compara os dois modelos exatamente nas mesmas 30 questões.
Detalhamento completo por ano, habilidade e déficit restante:
Doc/RELATORIO_BASE_v3.md no repositório GitHub (gerado por
src/relatorio_base.py, não editado à mão).
Formato do exemplo (chat SFT, inalterado): system / user / assistant
como no L2-2026-09 — ver o repositório GitHub para o schema exato.
Procedimento de treinamento
Setup QLoRA inalterado (mesmo código, mesmos hiperparâmetros do
L2-2026-09) — só o dataset mudou, o que mantém a comparação com o
baseline_v1 interpretável como efeito dos dados, não da receita.
| Hiperparâmetro | Valor |
|---|---|
| Modelo base | unsloth/Qwen3-1.7B (unsloth/qwen3-1.7b-unsloth-bnb-4bit) |
max_seq_length | 1024 |
| LoRA rank / alpha | 16 / 32 |
| Target modules | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
| Batch efetivo | 2 × 8 de acumulação de gradiente = 16 |
| Learning rate | 2e-4, cosseno, warmup de 5% |
| Épocas | 3 (early stopping por eval_loss, patience 2) |
| Otimizador | adamw_8bit, weight decay 0,01 |
| Precisão | bf16 |
| Loss | apenas nos tokens do assistant |
| Seed | 42 |
eval_loss por época: 0,5899 → 0,5614 (melhor, época 2) → 0,5631.
train_loss final: 0,4691.
Avaliação
Gates de promoção (src/promover_checkpoint.py --perfil multiseed)
O gate do L2-2026-09 rodava uma passada sobre as 30 amostras do
conjunto congelado. Reproduzido para a v3, deu um único item
(MT9027MH06MT, 9º ano H06) reprovando G2 (falha irrecuperável) E G3
(consistência resposta↔resolução) ao mesmo tempo — em 30 amostras, um item
só vale 10 pontos percentuais de G3. Revisado em 05/10/2026, registrado
como revisão feita depois de um veredito reprovado (mesma prática já usada
no G4 em 16/09/2026 e no G11 em 30/09/2026): o gate agora exige ≥3
rodadas independentes de seed (90 amostras pareadas, mesmos prompts,
mesmo pareamento por item, seeds de geração diferentes) e decide G2/G3 por
McNemar pareado mais um teto absoluto de 5% de falha, em vez de uma
passada sobre 30 itens. Todos os outros gates (G1, G4–G8) continuam
precisando passar em todas as rodadas — a revisão não afrouxa mais nada.
| Gate | Critério | Resultado (90 amostras pareadas, 3 rodadas) |
|---|---|---|
| G1 Contrato estrutural | ≥ 99% e ≥ baseline, todas as rodadas | passou nas 3 rodadas |
| G2* Falhas após best-of-N | sem piora significativa (McNemar) e ≤ 5% absoluto | 2/90 → 2/90 (2,2%); 2 piora, 2 melhora, p = 1,000 |
| G3* Consistência | sem piora significativa (McNemar) | 100% (n=31) → 94,3% (n=35); 2 piora, 0 melhora, p = 0,500 |
| G4 Dependência visual | sem piora significativa, todas as rodadas | passou nas 3 rodadas |
| G5 Aderência à dificuldade | ≥ baseline − 5pp, todas as rodadas | passou nas 3 rodadas |
| G6 Viés de gabarito | ≤ baseline + 5pp, todas as rodadas | passou nas 3 rodadas |
| G7 Esquecimento (5º e 9º ano) | sem regressão estrutural, todas as rodadas | passou nas 3 rodadas |
| G8 Velocidade | ≥ baseline − 10%, todas as rodadas | passou nas 3 rodadas |
| G9 Perplexidade (informativo) | — | não medida |
Decisão: PROMOVIDO. Ganhos: amostras verificáveis por máquina 31 → 35
(de 90); viés de gabarito (letra mais frequente) 43,8% → 30,0%.
Veredito completo, relatório por rodada e detalhe dos pares McNemar em
outputs/relatorios/VEREDITO_v3_multiseed.json no repositório GitHub.
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: 9º ano. Habilidade: H06 — Cálculo com números reais: adição, subtração, multiplicação e divisão.. Dificuldade: Moderado."},
]
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))
GGUF com llama.cpp (offline / mobile)
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: 5º ano. Habilidade: H08 — ... . Dificuldade: Difícil."
Combine sempre a grammar GBNF com generate_validated()
(tests/test_model.py), que verifica estrutura, consistência e dependência
visual antes de entregar a questão, regenerando quando necessário.
Limitações
- A verificação matemática do 9º ano ainda é fraca. O verificador simbólico de geometria roda em modo sombra (abstém-se em ~40% dos itens de classificação do 9º ano) e ainda não bloqueia nem regenera nada. Os dois itens de falha irrecuperável do gate da v3 (rodadas de seed 0 e 1) foram ambos do 9º ano. Promover o verificador a modo ativo depende de uma auditoria humana cega de uma amostra nova — ainda não feita.
- 34 das 79 habilidades ainda estão abaixo da meta de ≥30 itens após a
rodada de fechamento de déficit (abaixo do déficit inicial, maior). A
carência se concentra no 2º, 5º e 9º ano — ver a tabela por habilidade em
Doc/RELATORIO_BASE_v3.md. As tentativas de geração para as habilidades restantes esbarraram majoritariamente nos mesmos filtros (subtema divergente, erro de gabarito), não em esgotamento de orçamento — gastar mais chamadas de API nas mesmas habilidades dificilmente fecharia o déficit. - 72,3% dos exemplos de treino são destilados/injetados, não vêm do
banco real escrito por humanos (25,5%). A dupla revisão do pipeline de
agentes (mais o segundo revisor Gemini nos itens novos) é o principal
controle de qualidade; não foi validada contra uma auditoria humana
totalmente independente em escala — só uma amostra das remoções
automáticas e dos itens sinalizados para revisão humana
(
outputs/agentes/revisao_humana.jsonl) foi checada pontualmente. - Os ~1.234 itens aceitos em ciclos anteriores (antes de existir o segundo revisor Gemini) não têm voto do Gemini — só o par de juízes locais.
- A calibração dos modelos juízes é parcialmente in-sample (mesma família de modelo usada em partes do pipeline de geração).
- As medidas de velocidade vêm de CPU de desktop com 4 threads; latência e memória no dispositivo (celular) ainda não foram medidas nesta versão.
Uso pretendido e considerações éticas
O modelo apoia o professor; não o substitui. Questões geradas devem ser revisadas por um educador antes de chegar aos alunos, sobretudo no 9º ano, onde a verificação automática é mais fraca. Não é projetado para avaliações de alto impacto.
Proveniência dos dados
As questões vêm do banco interno no padrão SAEB, de um lote entregue por
colaboradores (L2-2026-09) e do pipeline de agentes de fechamento de
déficit da v3 (gerador + validador e revisor cegos + árbitro Gemini nas
remoções + segundo revisor Gemini nas injeções novas). Todo o código do
pipeline, os registros de decisão e a trilha de auditoria estão no
repositório GitHub indicado acima.
Citação
@misc{sarmanho2026qwen3saebv3,
author = {Sarmanho, Elton},
title = {{Qwen3-1.7B SAEB Math Question Generator} (v3):
Agent-Curated, Dual-Reviewed On-Device Generation of
Structured Multiple-Choice Mathematics Questions for
Brazilian Basic Education},
year = {2026},
month = oct,
howpublished = {\url{https://github.com/eltonsarmanho/TreinamentoModeloQuestoes}},
note = {QLoRA fine-tune of Qwen3-1.7B, data cycle v3,
GGUF Q4\_K\_M release, multiseed promotion gate}
}
@inproceedings{dettmers2023qlora,
author = {Dettmers, Tim and Pagnoni, Artidoro and Holtzman, Ari and Zettlemoyer, Luke},
title = {{QLoRA}: Efficient Finetuning of Quantized {LLMs}},
booktitle = {Advances in Neural Information Processing Systems},
volume = {36},
year = {2023}
}
@misc{qwen3technicalreport,
author = {{Qwen Team}},
title = {{Qwen3} Technical Report},
year = {2025},
eprint = {2505.09388},
archivePrefix = {arXiv},
primaryClass = {cs.CL}
}