File size: 10,109 Bytes
932cff2
 
 
 
 
 
 
 
 
 
 
 
 
 
5afc2f6
932cff2
 
 
5afc2f6
 
932cff2
 
5afc2f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932cff2
 
 
 
 
 
 
 
 
 
5afc2f6
932cff2
5afc2f6
932cff2
 
 
5afc2f6
 
932cff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5afc2f6
932cff2
5afc2f6
 
 
 
 
 
932cff2
5afc2f6
5cdfd30
5afc2f6
 
 
 
 
 
 
 
 
 
 
 
5cdfd30
932cff2
 
 
 
 
 
 
5afc2f6
932cff2
5afc2f6
932cff2
 
5afc2f6
 
932cff2
 
 
 
5afc2f6
 
 
 
 
 
 
 
 
932cff2
 
 
 
 
 
 
5afc2f6
 
 
 
 
 
932cff2
dda2531
5afc2f6
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
---
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.