Commit Graph
18 Commits
Author SHA1 Message Date
aleleba 77e6804a4f Fase 5: gate3 - subir max_tokens a 2048 y guardar texto completo de respuestas
Mejoras permanentes al test, no solo para esta corrida:

1. max_tokens: 512 -> 2048 (configurable via GATE3_MAX_TOKENS). Con
--reasoning-parser activo, un modelo de razonamiento puede agotar 512
tokens pensando antes de emitir el contenido final -- la respuesta queda
cortada a mitad de razonamiento y check_adherencia() no encuentra ninguna
substring esperada, un FAIL por presupuesto de tokens agotado, no por
adherencia real. El caso que fallaba en las corridas NVFP4 de Fase 5
(aleleba-pr/adherencia, el prompt mas abierto de los tres) es sospechoso
de este defecto -- el JSON de resultados no guardaba el texto de la
respuesta, asi que no se podia auditar.

2. Guardar content+reasoning completos en cada fila del JSON de resultados
(antes solo se guardaba passed/hits/tool_calls). Permite auditar con
criterio humano cualquier fallo futuro sin tener que re-correr el test.
2026-07-30 06:35:11 +00:00
aleleba be90e51214 Fase 5: 21_quantize_nvfp4.py - separar preparacion de calibracion en proceso aparte
Correccion de diagnostico: la conclusion anterior ("incluir ultrachat_200k
dispara el OOM") era incorrecta. Evidencia: la corrida v2 (NUM_CALIBRATION_
SAMPLES=1024, SIN ultrachat) crasheo con el mismo CUDA OOM exacto, en el mismo
punto exacto del setup de oneshot() (disable_lm_head onload), mientras que la
corrida v3 (512 muestras propias) habia progresado bien mas alla de ese mismo
punto (trace_subgraphs completo) antes de ser detenida manualmente. El patron
real es presion de memoria total acumulada en el pool unificado del GB10
(modelo mmap'd de 67GB + construccion del checkpoint cuantizado + allocations
CUDA + maquinaria de `datasets`/pyarrow para ultrachat), no una propiedad
especifica de ultrachat_200k.

Fix: separar la preparacion de datos de calibracion (que puede requerir
`datasets`/streaming/red para ultrachat) de la cuantizacion (que carga el
modelo completo) en dos procesos distintos. --prepare-calibration construye y
guarda a disco (CALIBRATION_CACHE_PATH) la muestra ya tokenizada SIN cargar el
modelo; la cuantizacion normal detecta el cache y lo carga desde disco (sin
volver a tocar `datasets`/red) antes de cargar el modelo. Se agrega tambien un
gc.collect() explicito antes de cargar el modelo.
2026-07-30 05:09:22 +00:00
aleleba b246f8d97a Fase 5: 21_quantize_nvfp4.py - cargar ultrachat_200k en modo streaming
3 intentos seguidos de calibracion mezclada (256 ultrachat + 256 propias)
crashearon con el mismo CUDA OOM reproducible, siempre en el mismo punto
exacto (setup interno de oneshot(): trace_subgraphs/disable_lm_head), tanto
con MAX_SEQUENCE_LENGTH=8192 como =2048 -- descartando el largo de secuencia
como causa. La unica variable real frente a los intentos que SI funcionaron
(256 muestras solo propias) es la inclusion de ultrachat_200k.

Causa raiz identificada: load_dataset(..., split="train_sft") sin streaming
materializa el split completo (~208k ejemplos) como Arrow local, y ademas
genera las 4 splits del repo (~2.9GB en disco). En este hardware (GB10,
memoria unificada CPU/GPU) ese cache extra parece ser suficiente para
empujar el proceso sobre el limite justo en el momento de mayor presion de
memoria del setup de oneshot(). Fix: cargar con streaming=True + shuffle de
buffer + take(n), que solo trae los N ejemplos necesarios sin materializar
el dataset completo -- probado de forma aislada (256 ejemplos en ~12s, sin
crecimiento de cache en disco).
2026-07-30 04:00:07 +00:00
aleleba 78d9b0d90d Fase 5: 21_quantize_nvfp4.py - soporte para calibracion mezclada con ultrachat_200k
Nueva variable NUM_ULTRACHAT_SAMPLES (default 0, sin cambio de comportamiento):
cuando > 0, mezcla esa cantidad de muestras de HuggingFaceH4/ultrachat_200k
(split train_sft, el mismo corpus/split que uso RedHatAI en su receta de
referencia) con (NUM_CALIBRATION_SAMPLES - NUM_ULTRACHAT_SAMPLES) muestras de
TRAIN_DATA_PATH, concatenadas y mezcladas (shuffle, seed=42) antes de
tokenizar para calibracion.

Hipotesis a probar (decision del usuario tras ver que el aislamiento sin
--speculative-config descarto al speculative decoding como causante de la
regresion, y antes de simplemente aumentar la cantidad de muestras propias):
la regresion podria venir de poca DIVERSIDAD tematica en la calibracion
(solo conversaciones angostas de los 5 MCPs/skills del proyecto) en vez de
poca cantidad de muestras. La porcion de TRAIN_DATA_PATH sigue usando el
mismo seed=42, asi que con NUM_ULTRACHAT_SAMPLES=256 y
NUM_CALIBRATION_SAMPLES=512, las 256 muestras propias son identicas a las
del primer intento (256 solo propias).
2026-07-30 03:17:12 +00:00
aleleba 9f073034e4 Fase 5: 21_quantize_nvfp4.py - corregir verificacion de tensores de vision
La verificacion automatica asumia (siguiendo el layout del checkpoint de
referencia de RedHatAI) que save_pretrained() separaria vision a su propio
model_visual.safetensors. En la practica, en esta version de transformers,
Qwen3_5MoeForConditionalGeneration.save_pretrained() escribe lenguaje+vision
juntos en el/los shard(s) de model.safetensors -- comportamiento igualmente
valido (el index.json mapea cada tensor a su shard real sin importar el
nombre de archivo). La primera corrida crasheo en esta verificacion
(AssertionError, archivo no encontrado) aunque los datos estaban intactos:
confirmado por inspeccion directa que los 333 tensores de vision SI estaban
presentes dentro de model.safetensors. Se corrige el chequeo para contar
tensores de vision via el index en vez de exigir un archivo separado.

Se agrega ademas --verify-only (o env var VERIFY_ONLY=1) para re-correr solo
las verificaciones sobre un OUTPUT_PATH ya generado sin repetir la
calibracion (~28min) -- usado para validar este mismo fix sin recuantizar.

Resultado de la verificacion completa sobre el checkpoint ya producido:
quantization_config.format=nvfp4-pack-quantized, model_mtp.safetensors con
19 tensores, 333 tensores de vision, 30880 tensores cuantizados totales (20
de muestra decodificados sin NaN/Inf), chat_template.jinja identico al de
produccion, tamano total 23.35GB.
2026-07-29 23:58:57 +00:00
aleleba c9878ef98f Fase 5: puertas 2-4 - permitir results filename configurable via env var
Para reusar los scripts de Fase 4 contra el endpoint NVFP4 nuevo sin
sobreescribir los resultados de Fase 4 (data/gate{2,3,4}_results.json, ya
commiteados como baseline de comparacion). Default sin cambios cuando la
env var no esta seteada.
2026-07-29 22:39:23 +00:00
aleleba 1e3726a12f Fase 5: 21_quantize_nvfp4.py - compat shim para bug de import en llmcompressor 0.12.0
llmcompressor 0.12.0 (ultima version en PyPI) importa incondicionalmente
GraniteMoeParallelExperts al armar su registro interno de arquitecturas MoE
linearizables, incluso para modelos que no son GraniteMoe. transformers 5.14.1
renombro esa clase a GraniteMoeExperts, lo que rompia oneshot() para
cualquier modelo (incluido este Qwen3.5 MoE, que ni siquiera esta en ese
registro). Alias minimo antes de importar llmcompressor para que el import
no explote; nunca se usa en la practica ya que Qwen3.5 MoE no matchea esa
entrada del registro.
2026-07-29 22:39:12 +00:00
aleleba 712097e26b Fase 5: scripts/21_quantize_nvfp4.py - cuantizacion NVFP4 via llm-compressor
Clona la receta exacta de RedHatAI/Qwen3.6-35B-A3B-NVFP4 (QuantizationModifier
targets=Linear scheme=NVFP4, ignore list identica) sobre el checkpoint mergeado
de Fase 4. Diferencia obligatoria: calibra con una muestra de data/train.jsonl
(chat template de produccion) en vez de ultrachat_200k. moe_calibrate_all_experts=True
para cubrir los 256 expertos ruteados. Reinyecta MTP via
save_mtp_tensors_to_checkpoint y deja que save_pretrained separe vision a su
propio shard. Verificaciones automaticas: quantization_config.format,
conteo de tensores MTP/vision, muestra sin NaN/Inf tras des-cuantizar,
chat_template.jinja de produccion preservado.
2026-07-29 22:10:41 +00:00
aleleba 2762761a8e Fase 4: puerta 1 - reportar tambien loss ponderado por token (comparable a Trainer)
El primer resultado (promedio simple por ejemplo) daba 0.5185 vs 0.275 de Fase 3, señal de
alarma segun el propio script. La causa era metodologica, no un bug de merge: el bucket
replay concentra 112927 de los ~128849 tokens assistant del split de eval (87%), mientras
que buckets dificiles como negativos/skills_adherencia/delegacion_subagentes tienen pocos
ejemplos pero loss alto -- un promedio por ejemplo les da el mismo peso que a replay,
inflando el global. transformers.Trainer pondera por token, no por ejemplo. Con el mismo
ponderado por token: 0.2560 vs 0.275 de Fase 3 (diff=0.019, dentro del margen esperado) --
confirma que el merge es correcto.
2026-07-29 17:49:08 +00:00
aleleba ceba5f80cd Fase 4: puerta 1 (eval-loss offline por bucket) y contenedor/scripts de puertas 2-4
- scripts/30_eval_suite.py --gate 1: eval-loss sobre el checkpoint mergeado, agrupado por
  meta.bucket (aislando replay), comparado contra eval_loss=0.275 de Fase 3.
- docker-compose.eval.yml: servicio vllm-eval propio (puerto 8001), sirviendo el
  checkpoint mergeado en BF16, con tool-call-parser=qwen3_coder y reasoning-parser=qwen3.
  No se pudo leer el compose real de produccion (/data/compose/43/docker-compose.yml no
  existe en spark, probablemente vive en el host del servidor Portainer) -- flags basados
  en la arquitectura conocida del modelo.
- scripts/31_build_holdout_prompts.py: genera data/holdout_prompts.jsonl (200 prompts,
  40 por MCP, sin overlap verificado contra train.jsonl/eval.jsonl).
- scripts/32_gate2_toolcalls.py: valida tool-calls devueltas por vllm-eval (parser real
  de vLLM, nunca una regex propia) contra los 200 prompts held-out.
- scripts/33_gate3_adherencia.py: checklists de adherencia por skill + no-activacion,
  con baseline opcional contra vllm-qwen36 si esta corriendo.
- scripts/34_gate4_e2e.py: arma el plan de llamadas E2E contra los 5 MCPs y 5 skills via
  el checkpoint mergeado, para que el agente orquestador las ejecute con sus MCPs reales.
2026-07-29 17:37:02 +00:00
aleleba 268451aed7 Fase 4: scripts/20_merge_lora.py - merge streaming shard-a-shard del LoRA sobre el checkpoint base
Remapea nombres de modulo (model.layers.* del adapter -> model.language_model.layers.*
del checkpoint base multimodal), preserva tensores mtp.*/visual.* al copiarlos sin
modificar, y toma chat_template.jinja de MODEL_PATH (nunca del adapter, que tiene el
template de masking de training). Verificacion automatica en verde: 310/310 tensores
LoRA-target mergeados, 1045/1045 tensores totales preservados, sin NaN/Inf, muestra de
200 tensores no-target byte-identica al base.
2026-07-29 17:28:36 +00:00
aleleba c4a0c404aa Fase 3: scripts/10_train.py - entrenamiento LoRA con masking manual, target_modules confirmados (incluye out_proj de Gated DeltaNet) 2026-07-29 04:33:29 +00:00
aleleba 3f14aa5cec Fase 2: 06_validate_dataset.py - validacion con tokenizer/chat_template real (1470/1470 ok, 0 excepciones, 0 filtrados, 0 secretos)
El chat_template.jinja de produccion no tiene tags {% generation %}, por lo que
return_assistant_tokens_mask salia vacio para el 100% de los ejemplos en el primer run.
Se genero data/chat_template_train.jinja (copia exacta del template real, con {%- generation -%}
envolviendo solo el contenido/tool_calls/im_end de cada turno assistant) para el fallback
de masking ya anticipado en la Decision de diseno #4 del plan principal -- el chat_template.jinja
original no se toca, sigue siendo el que sirve produccion.
2026-07-29 00:56:23 +00:00
aleleba ab5d78b5c8 Fase 2: 05_build_dataset.py - ensamblado v1 (1470 ejemplos: 750 nuevos + 720 replay, split 90/10 estratificado por bucket) 2026-07-29 00:50:10 +00:00
aleleba 837f91060f Fase 2: 04_sanitize.py - scrubbing de secretos de skills/agentes/plans (10 secretos unicos, gate en verde) 2026-07-29 00:35:59 +00:00
aleleba ef35472398 Fix: OUTPUT_PATH de 03_build_replay.py apuntaba a /workspace/... (ruta de contenedor inexistente en el host)
El script debe correr directo en el host de spark (sin contenedor), donde /workspace no existe.
Corrige a la ruta real del repo en el NFS compartido: /mnt/docker-nas/projects/ai-projects/qwen3-6-lora/data/raw/replay.jsonl
2026-07-28 12:51:55 +00:00
aleleba b2aa855a70 Fase 1: schemas de los 5 MCPs y script de generacion de replay
data/schemas/*.json: dump fiel de las tool definitions reales de
Penpot (4), Gitea (53), GitHub-personal (43), Docmost (17) y
Atlassian (37), obtenidas directo de las definiciones ya cargadas
en la sesion de Claude Code (no se escribio un cliente MCP nuevo,
para no arriesgar desviarse del esquema real).

PENPOT_DEPLOYMENT_NOTES.md: investigacion del codigo fuente oficial
de @penpot/mcp confirma que import_image y export_shape.filePath
estan ausentes porque este deployment corre en modo remoto/multi-
usuario (isFileSystemAccessEnabled() = !isRemoteMode()) -- documentado
tambien en una subpagina nueva de Docmost.

scripts/03_build_replay.py: genera ~700 ejemplos de replay (anti-
forgetting) contra el vLLM de produccion, 80 prompts semilla
(conversacion general/codigo/razonamiento) x 9 pasadas variando
temperatura. Mapea el campo "reasoning" de la API de vLLM a
"reasoning_content" para la convencion de chat template.
2026-07-28 05:34:55 +00:00
aleleba d121c4465e Fase 0: scripts de verificacion de hardware e inspeccion de modulos
00_verify_hardware.py: flash_attn, sdpa y flash-linear-attention
funcionan los tres en el GB10 (SM121) via Triton JIT -- mejor de lo
esperado, ya no hace falta el fallback lento de PyTorch para Gated
DeltaNet. attn_implementation recomendado: flash_attention_2.

01_inspect_modules.py: resuelve la discrepancia de nombres de las
proyecciones de Gated DeltaNet inspeccionando la arquitectura real
(device_map=meta, sin pesos). Resultado: parcialmente fusionado --
in_proj_qkv (q+k+v en un solo Linear) pero in_proj_z, in_proj_a e
in_proj_b por separado. Confirma tambien que mlp.experts.{gate_up,down}_proj
son nn.Parameter 3D (no LoRA-ables) y que shared_expert.{gate,up,down}_proj
son nn.Linear normales, como anticipaba el plan.

constraints.txt: fija la version exacta de torch de la imagen NGC
(2.10.0a0+b4e4ee81d3.nv25.12) para instalar transformers/peft/trl/
accelerate/bitsandbytes sin romper el build ARM64/Blackwell.
2026-07-28 03:52:10 +00:00