Pear Labs
v1.1: add real C++ training data, fix eos_token_id, add MMLU/MMLU-Pro/MMLU-Redux + best-practice inference config
5afc2f6 | license: other | |
| base_model: Qwen/Qwen3.5-4B | |
| tags: | |
| - code | |
| - lora | |
| - qlora | |
| - qwen3 | |
| - pear-coder-mini | |
| language: | |
| - en | |
| - es | |
| # Pear Coder Mini (v1.1) | |
| Pear Coder Mini es un asistente de código desarrollado por **Pear Labs**, basado en | |
| **Qwen3.5-4B** y afinado con QLoRA (rank 32, rsLoRA) sobre una mezcla de datos de | |
| código general, C++, razonamiento sobre código, COBOL y datos de instrucción | |
| general bilingüe (inglés/español) para reducir el olvido catastrófico. | |
| Este repositorio contiene el **modelo fusionado** (adapter LoRA + pesos base ya | |
| combinados) listo para servir directamente. La versión anterior (v1.0) sigue | |
| disponible en la revisión de git `v1.0` de este mismo repo. | |
| ## Qué cambió en v1.1 | |
| v1.0 mostraba un desempeño en C++ notablemente por debajo de Python/JS (21.1% | |
| pass@1 en MultiPL-E C++). Se agregó una porción real de C++ | |
| ([codeparrot/xlcost-text-to-code](https://huggingface.co/datasets/codeparrot/xlcost-text-to-code), | |
| config `C++-program-level`, detokenizado y reformateado con `clang-format`), | |
| **reemplazando** una porción equivalente del bucket generalista (no se agregó | |
| dataset extra, se mantuvo el tamaño total ~8,955 ejemplos y la proporción | |
| 60/25/15 código/razonamiento/general) para no diluir lo que ya funcionaba bien | |
| ni alargar el tiempo/costo de entrenamiento. | |
| | Benchmark | v1.0 | v1.1 | Δ | | |
| |---|---|---|---| | |
| | HumanEval (n=164) | 56.7% | 57.9% | +1.2 | | |
| | HumanEval+ (n=164) | 56.7% | 57.9% | +1.2 | | |
| | MBPP sanitized (n=257) | 58.4% | 63.4% | +5.0 | | |
| | MBPP+ (n=378) | 66.4% | 66.1% | -0.3 | | |
| | MultiPL-E JavaScript (n=161) | 52.8% | 50.9% | -1.9 | | |
| | **MultiPL-E C++ (n=161)** | **21.1%** | **29.2%** | **+8.1** | | |
| | MMLU (5-shot, n=14,042) | no medido | 68.9% | — | | |
| | MMLU-Pro (5-shot, n=12,032)* | no medido | 42.1% | — | | |
| | MMLU-Redux (5-shot, n=5,440) | no medido | 71.6% | — | | |
| | Instruction-Following-Mini (n=20) | no medido | 90.0% | — | | |
| Mejora clara en C++ (objetivo principal), MBPP, HumanEval; JS y MBPP+ se | |
| mantienen esencialmente parejos (variación dentro de ruido de muestreo). | |
| No se detectó evidencia de olvido catastrófico: MMLU/MMLU-Redux muestran | |
| capacidad general sólida para un 4B. | |
| \* **MMLU-Pro no es comparable directo al 79.1 oficial de Qwen3.5-4B** — ese | |
| número se mide con razonamiento explícito (CoT); aquí se midió con comparación | |
| directa de log-probabilidad (sin razonamiento) por velocidad, lo cual castiga | |
| mucho el resultado en este benchmark específico ya que fue diseñado para | |
| requerir varios pasos de razonamiento. No implica pérdida de capacidad real, | |
| implica que la metodología de medición no es la misma. Ver la nota sobre | |
| thinking mode más abajo — es evidencia directa de esto: con thinking mode | |
| activado, el pass@1 en código sube ~20 puntos. | |
| ## Buenas prácticas / configuración recomendada de inferencia | |
| Se corrió un barrido de configuraciones (greedy, temperatura/top_p, y thinking | |
| mode) sobre un subset de HumanEval para encontrar el punto dulce: | |
| | Configuración | pass@1 (n=40) | | |
| |---|---| | |
| | greedy (T=0), thinking OFF | 50.0% | | |
| | T=0.2, top_p=0.95 | 45.0% | | |
| | T=0.4, top_p=0.9 | 42.5% | | |
| | T=0.7, top_p=0.9 | 42.5% | | |
| | T=1.0, top_p=1.0 | 32.5% | | |
| | **thinking mode ON, greedy** | **70.0%** | | |
| **Recomendaciones:** | |
| - **Para tareas de código donde la precisión importa más que la latencia:** | |
| activa thinking mode (`enable_thinking=True` al aplicar el chat template) con | |
| `max_new_tokens>=2048` — el modelo necesita espacio para razonar antes de | |
| responder. Esto sube el pass@1 en código de 50% a 70%, al costo de ~5x más | |
| tokens generados y por lo tanto más latencia/costo. | |
| - **Para respuestas rápidas/baratas:** `enable_thinking=False`, greedy | |
| (`do_sample=False`), `max_new_tokens~400`. Es el modo usado en la mayoría de | |
| las evaluaciones de este repo. | |
| - **Evita temperatura alta para generación de código:** el pass@1 cae de forma | |
| monótona con la temperatura (50%→32.5% de T=0 a T=1.0). Si necesitas | |
| variabilidad (ej. para dar varias opciones), usa T=0.2-0.4 como máximo, no más. | |
| - El modelo **sí sabe cerrar correctamente el bloque `<think>...</think>`** | |
| cuando se le da presupuesto de tokens suficiente — un hallazgo temprano de | |
| "thinking mode roto" durante el desarrollo resultó ser un artefacto de medir | |
| con `max_new_tokens` demasiado bajo (400), no un defecto real del modelo. | |
| ## Entrenamiento | |
| - **Base:** Qwen/Qwen3.5-4B | |
| - **Método:** QLoRA (4-bit NF4), rank 32, rsLoRA (`use_rslora=True`), alpha=32 | |
| - **Módulos objetivo:** q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj | |
| - **Learning rate:** 1e-4, cosine schedule, warmup 3% | |
| - **Épocas:** 1 (sobre ~8,955 ejemplos) | |
| - **Hardware:** 1x RTX 3090 24GB (vast.ai) | |
| - **Longitud de secuencia:** 2048 tokens | |
| - **Pérdida final:** 0.732 (desde 1.19 inicial) · `mean_token_accuracy` final: 0.796 | |
| ### Mezcla de datos v1.1 (~8,955 ejemplos) | |
| | Categoría | Fuente | % aprox. | | |
| |---|---|---| | |
| | Código general | [sahil2801/CodeAlpaca-20k](https://huggingface.co/datasets/sahil2801/CodeAlpaca-20k), [glaiveai/glaive-code-assistant-v3](https://huggingface.co/datasets/glaiveai/glaive-code-assistant-v3) | ~46% | | |
| | **C++ real** (nuevo en v1.1) | [codeparrot/xlcost-text-to-code](https://huggingface.co/datasets/codeparrot/xlcost-text-to-code) (C++-program-level, reformateado con clang-format) | ~15% | | |
| | COBOL | [harshini-kumar/CobolCodeBench](https://huggingface.co/datasets/harshini-kumar/CobolCodeBench) | ~0.5% | | |
| | Razonamiento sobre código | [nvidia/OpenCodeReasoning](https://huggingface.co/datasets/nvidia/OpenCodeReasoning), [hkust-nlp/CodeIO-PyEdu-Reasoning](https://huggingface.co/datasets/hkust-nlp/CodeIO-PyEdu-Reasoning) | ~25% | | |
| | Instrucción general EN/ES (replay anti-olvido) | [databricks/databricks-dolly-15k](https://huggingface.co/datasets/databricks/databricks-dolly-15k), [argilla/databricks-dolly-15k-curated-multilingual](https://huggingface.co/datasets/argilla/databricks-dolly-15k-curated-multilingual) | ~13% | | |
| | Identidad (Pear Coder Mini / Pear Labs, EN/ES) | sintético | ~0.7% | | |
| Se evitaron deliberadamente datasets tipo "Alpaca" cuyas respuestas fueron generadas | |
| por modelos de OpenAI, por las restricciones de sus TOS sobre uso para entrenar | |
| modelos competidores. | |
| ## Nota importante de configuración (leer antes de servir el modelo) | |
| El `generation_config.json` de este repo fue corregido manualmente: el checkpoint | |
| base de Qwen3.5-4B trae por defecto `eos_token_id` apuntando a `<|endoftext|>`, pero | |
| el token real de cierre de turno de chat es `<|im_end|>`. Sin esta corrección el | |
| modelo no para de generar y alucina turnos de conversación falsos. Este repo ya | |
| incluye el fix (`eos_token_id: [248046, 248044]`); si conviertes o vuelves a | |
| exportar los pesos, conserva este ajuste. | |
| ## Resultados de evaluación completos | |
| Metodología: greedy decoding salvo donde se indique, pass@1 con ejecución real | |
| de tests sobre los sets **completos** (no subsets) de HumanEval, MBPP, HumanEval+, | |
| MBPP+ y MultiPL-E. MMLU/MMLU-Pro/MMLU-Redux con metodología estándar 5-shot, | |
| comparación de log-probabilidad (sin CoT, ver nota arriba). Evaluación propia | |
| (no el CLI oficial de `evalplus`/`lm-evaluation-harness`) pero replicando la | |
| misma metodología sobre los datasets públicos oficiales. | |
| | Benchmark | n | Resultado | | |
| |---|---|---| | |
| | HumanEval | 164 | 57.9% pass@1 | | |
| | HumanEval+ | 164 | 57.9% pass@1 | | |
| | MBPP sanitized | 257 | 63.4% pass@1 | | |
| | MBPP+ | 378 | 66.1% pass@1 | | |
| | MultiPL-E JavaScript | 161 | 50.9% pass@1 | | |
| | MultiPL-E C++ | 161 | 29.2% pass@1 | | |
| | MMLU (5-shot) | 14,042 | 68.9% accuracy | | |
| | MMLU-Pro (5-shot, sin CoT)* | 12,032 | 42.1% accuracy | | |
| | MMLU-Redux (5-shot) | 5,440 | 71.6% accuracy | | |
| | Instruction-Following-Mini | 20 | 90.0% | | |
| | COBOL (estructura correcta) | 3 | 100% | | |
| | Identidad EN/ES | 6 | nunca respondió "Qwen" ni otro nombre incorrecto | | |
| ## Uso | |
| ```python | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| import torch | |
| model = AutoModelForCausalLM.from_pretrained( | |
| "elianalfonsolopezpreciado/pearcodermini", torch_dtype=torch.bfloat16, device_map="auto" | |
| ) | |
| tokenizer = AutoTokenizer.from_pretrained("elianalfonsolopezpreciado/pearcodermini") | |
| messages = [{"role": "user", "content": "Write a python function that reverses a string."}] | |
| # Rapido/barato: | |
| text = tokenizer.apply_chat_template( | |
| messages, tokenize=False, add_generation_prompt=True, enable_thinking=False | |
| ) | |
| inputs = tokenizer(text, return_tensors="pt").to(model.device) | |
| out = model.generate(**inputs, max_new_tokens=400, do_sample=False) | |
| print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)) | |
| # Mayor precision en codigo (thinking mode, ver seccion de buenas practicas): | |
| text_thinking = tokenizer.apply_chat_template( | |
| messages, tokenize=False, add_generation_prompt=True, enable_thinking=True | |
| ) | |
| inputs = tokenizer(text_thinking, return_tensors="pt").to(model.device) | |
| out = model.generate(**inputs, max_new_tokens=2048, do_sample=False) | |
| print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)) | |
| ``` | |
| ## Limitaciones conocidas | |
| - Solo 1 época de entrenamiento sobre un dataset moderado (~9k ejemplos); hay | |
| margen para mejorar pass@1 con más datos/épocas si el presupuesto lo permite. | |
| - Los benchmarks se corrieron con arnés propio, no el CLI oficial de `evalplus` | |
| ni `lm-evaluation-harness` — recomendado revalidar ahí antes de citar las | |
| cifras en materiales de marketing/lanzamiento oficiales, especialmente MMLU-Pro | |
| (ver nota de metodología sin CoT arriba). | |
| - MBPP+ se evaluó con el campo `assertion` (test base), no el comparador | |
| diferencial completo `base_input`+`plus_input` del checker 100% oficial. | |
| - No se probó explícitamente resistencia a jailbreaks/seguridad adversarial. | |
| - No fue entrenado con formato de tool-calling/function-calling — no está pensado | |
| para uso agéntico (ej. SWE-bench, BFCL) tal cual, solo para generación/asistencia | |
| de código directa en chat. | |
| - C++ mejoró sustancialmente pero sigue por debajo de Python/JS (29.2% vs 50-58%) | |
| — sigue siendo el lenguaje más débil del modelo. | |