--- base_model: Qwen/Qwen2.5-7B-Instruct library_name: transformers license: apache-2.0 language: - es tags: - qlora - peft - conversational - education - spanish - build-small-hackathon pipeline_tag: text-generation --- # Sofía — Qwen2.5-7B-Instruct fine-tuneado para una compañera de voz de una nena de 3 años **TL;DR (EN):** QLoRA fine-tune of `Qwen/Qwen2.5-7B-Instruct` (merged, full precision weights) that teaches the base model to consistently play "Sofía" — a warm, short-sentence Spanish-speaking companion for a ~3 year old — to present curated content (``) **verbatim** instead of paraphrasing/inventing it, and to gently refuse/redirect unsafe topics. Built for the [Build Small Hackathon](https://huggingface.co/build-small-hackathon) (track *Backyard AI*, mérito *Well-Tuned*). Powers the [`sofia-educational-companion`](https://huggingface.co/spaces/build-small-hackathon/sofia-educational-companion) Space. ## Qué es Este modelo es el "pegamento conversacional" de **Sofía**, una compañera educativa por voz para una niña de ~3 años (proyecto Lumi, Build Small Hackathon, track Backyard AI). El LLM **nunca** es la fuente de hechos: todo el contenido (cuentos, actividades, números) vive curado en `content/` y se le inyecta al modelo entre `...`. Este fine-tune entrena **estilo y seguridad**, no conocimiento. Objetivos del fine-tune: - Mantener siempre la persona "Sofía": cálida, frases muy cortas, español rioplatense, una pregunta por turno. - Presentar el contenido curado **verbatim** (sin parafrasear ni inventar), incluso cuando se le pide un cuento/actividad que no existe en `content/`. - Rechazar o redirigir con cariño temas no aptos para una niña de 3 años, generalizando más allá de los términos exactos de `safety/blocklist.txt` (defensa en profundidad). ## Datos de entrenamiento `finetune/build_dataset.py` genera `finetune/dataset.jsonl`: **196 ejemplos** en formato chat (mismo `SYSTEM_PROMPT` y los mismos bloques ``/`` que construye `llm/engine.py` en producción), mezclando: - saludos y charla / persona, - entrega de actividades curadas (contar, formas, colores, animales), x3 frases cada una, - cambio de color de Sofía (intent `sofia_color`), - cuentos curados, - ~62 rechazos/redirecciones de temas no aptos que **no** repiten los términos exactos de la blocklist. ## Entrenamiento QLoRA sobre `Qwen/Qwen2.5-7B-Instruct`, corrido en Modal (GPU A10G): - Carga en 4-bit NF4 (`bnb_4bit_use_double_quant=True`), cómputo en bf16. - LoRA `r=16`, `alpha=32`, `target_modules=["q_proj", "k_proj", "v_proj", "o_proj"]`. - 3 épocas, batch size 2, gradient accumulation 4, learning rate 2e-4. - 72 steps, ~408s en A10G. Loss 2.51 → 0.14. - El adapter se mergeó (`merge_and_unload`) y se publicó en este repo como pesos completos en safetensors. Código: `finetune/train_modal.py` y `finetune/merge_lora.py` en el [repo del proyecto](https://huggingface.co/spaces/build-small-hackathon/sofia-educational-companion). ## Evaluación (smoke test) `finetune/smoke_test_modal.py` corrió 5 turnos representativos sobre este modelo ya mergeado: saludo, entrega de `` de conteo, una pregunta sobre un arma, miedo a los monstruos, y un pedido de cuento inventado ("un dragón que come autos"). Resultado: saluda en persona, repite el `` **verbatim** (a diferencia de alternativas probadas como Nemotron-mini/Nemotron-3-nano, que parafraseaban o inventaban), rechaza el tema del arma sin engancharse, redirige el miedo con calidez, y ante el cuento inventado dice que no lo tiene y ofrece una alternativa curada en vez de inventar una. ## Cómo usarlo ```python import torch from transformers import AutoModelForCausalLM, AutoTokenizer MODEL_ID = "build-small-hackathon/sofia-qwen2.5-7b" tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) model = AutoModelForCausalLM.from_pretrained( MODEL_ID, torch_dtype=torch.bfloat16 ).to("cuda") # SYSTEM_PROMPT exacto y bloques /: ver llm/engine.py messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "Hola Sofía"}, ] text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) inputs = tokenizer(text, return_tensors="pt").to("cuda") output = model.generate( **inputs, max_new_tokens=120, do_sample=True, temperature=0.6 ) ``` ## Limitaciones - Entrenado para **un caso de uso muy acotado**: compañera de juego para una niña de 3 años, en español rioplatense, con contenido inyectado por el sistema (``/``/``). Fuera de ese contexto se comporta básicamente como el Qwen2.5-7B-Instruct base. - No agrega conocimiento factual nuevo: por diseño, los hechos siguen viviendo en `content/`, nunca en los pesos. - Dataset chico (196 ejemplos), curado a mano para un hackathon — no es un benchmark de seguridad general. ## Licencia Apache 2.0, igual que el modelo base `Qwen/Qwen2.5-7B-Instruct`.