--- 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 `...`** 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.