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.
This commit is contained in:
@@ -50,6 +50,18 @@ Algoritmo:
|
|||||||
|
|
||||||
Soporta --verify-only (o env var VERIFY_ONLY=1) para re-correr solo las
|
Soporta --verify-only (o env var VERIFY_ONLY=1) para re-correr solo las
|
||||||
verificaciones sobre un OUTPUT_PATH ya generado, sin repetir la calibracion.
|
verificaciones sobre un OUTPUT_PATH ya generado, sin repetir la calibracion.
|
||||||
|
|
||||||
|
Soporta --prepare-calibration (o env var PREPARE_CALIBRATION=1) para construir y
|
||||||
|
guardar en disco (CALIBRATION_CACHE_PATH) la muestra de calibracion ya tokenizada,
|
||||||
|
SIN cargar el modelo. Corre esto en un proceso APARTE, antes de la cuantizacion
|
||||||
|
normal (que detecta el cache y lo carga en vez de reconstruirlo). Motivo: cargar
|
||||||
|
`datasets`/streamear ultrachat_200k y cargar el modelo de 67GB en el MISMO proceso
|
||||||
|
empuja la presion de memoria del pool unificado del GB10 justo al borde --
|
||||||
|
confirmado empiricamente que hasta una corrida SIN ultrachat (NUM_CALIBRATION_SAMPLES
|
||||||
|
=1024, solo TRAIN_DATA_PATH) crasheo con el mismo CUDA OOM que las corridas
|
||||||
|
mezcladas, en el mismo punto exacto del setup interno de oneshot()
|
||||||
|
(disable_lm_head onload). Separar los dos procesos evita que la maquinaria de
|
||||||
|
`datasets`/pyarrow/red conviva en el mismo proceso que el modelo cargado.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -97,6 +109,19 @@ ULTRACHAT_DATASET = "HuggingFaceH4/ultrachat_200k"
|
|||||||
ULTRACHAT_SPLIT = "train_sft"
|
ULTRACHAT_SPLIT = "train_sft"
|
||||||
MAX_SEQUENCE_LENGTH = int(os.environ.get("MAX_SEQUENCE_LENGTH", "8192"))
|
MAX_SEQUENCE_LENGTH = int(os.environ.get("MAX_SEQUENCE_LENGTH", "8192"))
|
||||||
CALIBRATION_SEED = 42
|
CALIBRATION_SEED = 42
|
||||||
|
# Preparar la muestra de calibracion (que puede requerir descargar/streamear
|
||||||
|
# ultrachat_200k via `datasets`/pyarrow/red) y cuantizar (que carga el modelo de
|
||||||
|
# 67GB completo) en el MISMO proceso empuja la presion de memoria del pool
|
||||||
|
# unificado GB10 justo al borde -- confirmado empiricamente: incluso una corrida
|
||||||
|
# de solo TRAIN_DATA_PATH con NUM_CALIBRATION_SAMPLES=1024 (sin ultrachat) crasheo
|
||||||
|
# con el mismo OOM que las corridas mezcladas, en el mismo punto exacto del setup
|
||||||
|
# de oneshot() (disable_lm_head). CALIBRATION_CACHE_PATH separa ambos pasos en dos
|
||||||
|
# procesos: --prepare-calibration construye y guarda el dataset ya tokenizado SIN
|
||||||
|
# cargar el modelo; la cuantizacion normal lo carga desde disco (sin volver a
|
||||||
|
# tocar `datasets`/red) antes de cargar el modelo.
|
||||||
|
CALIBRATION_CACHE_PATH = Path(
|
||||||
|
os.environ.get("CALIBRATION_CACHE_PATH", "/workspace/ft-models/nvfp4_calibration_cache")
|
||||||
|
)
|
||||||
|
|
||||||
# Receta identica a la de RedHatAI/Qwen3.6-35B-A3B-NVFP4 (recipe.yaml leido por SSH,
|
# Receta identica a la de RedHatAI/Qwen3.6-35B-A3B-NVFP4 (recipe.yaml leido por SSH,
|
||||||
# citado integro en PLAN.md). Las capas Gated DeltaNet (linear_attn) quedan en BF16
|
# citado integro en PLAN.md). Las capas Gated DeltaNet (linear_attn) quedan en BF16
|
||||||
@@ -374,26 +399,70 @@ def parse_args():
|
|||||||
default=os.environ.get("VERIFY_ONLY", "") not in ("", "0", "false", "False"),
|
default=os.environ.get("VERIFY_ONLY", "") not in ("", "0", "false", "False"),
|
||||||
help="saltar calibracion/guardado, solo re-correr las verificaciones sobre OUTPUT_PATH ya existente",
|
help="saltar calibracion/guardado, solo re-correr las verificaciones sobre OUTPUT_PATH ya existente",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--prepare-calibration",
|
||||||
|
action="store_true",
|
||||||
|
default=os.environ.get("PREPARE_CALIBRATION", "") not in ("", "0", "false", "False"),
|
||||||
|
help=(
|
||||||
|
"solo construir y guardar en disco (CALIBRATION_CACHE_PATH) la muestra de "
|
||||||
|
"calibracion ya tokenizada, SIN cargar el modelo -- correr en un proceso aparte "
|
||||||
|
"antes de la cuantizacion, para que `datasets`/streaming/red nunca compartan "
|
||||||
|
"proceso con el modelo de 67GB"
|
||||||
|
),
|
||||||
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_calibration():
|
||||||
|
print(f"[INFO] TRAIN_DATA_PATH={TRAIN_DATA_PATH}")
|
||||||
|
print(f"[INFO] NUM_CALIBRATION_SAMPLES={NUM_CALIBRATION_SAMPLES} MAX_SEQUENCE_LENGTH={MAX_SEQUENCE_LENGTH}")
|
||||||
|
print(f"[INFO] NUM_ULTRACHAT_SAMPLES={NUM_ULTRACHAT_SAMPLES}")
|
||||||
|
print(f"[INFO] cargando tokenizer desde {MODEL_PATH} (solo tokenizer, no el modelo)")
|
||||||
|
processor = AutoProcessor.from_pretrained(str(MODEL_PATH), trust_remote_code=True)
|
||||||
|
tokenizer = processor.tokenizer
|
||||||
|
|
||||||
|
calibration_dataset = load_calibration_dataset(tokenizer)
|
||||||
|
|
||||||
|
CALIBRATION_CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
calibration_dataset.save_to_disk(str(CALIBRATION_CACHE_PATH))
|
||||||
|
print(f"[INFO] muestra de calibracion guardada en {CALIBRATION_CACHE_PATH} ({len(calibration_dataset)} ejemplos)")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
print(f"[INFO] MODEL_PATH={MODEL_PATH}")
|
print(f"[INFO] MODEL_PATH={MODEL_PATH}")
|
||||||
print(f"[INFO] OUTPUT_PATH={OUTPUT_PATH}")
|
print(f"[INFO] OUTPUT_PATH={OUTPUT_PATH}")
|
||||||
|
|
||||||
|
if args.prepare_calibration:
|
||||||
|
prepare_calibration()
|
||||||
|
return
|
||||||
|
|
||||||
if args.verify_only:
|
if args.verify_only:
|
||||||
print("[INFO] --verify-only: saltando calibracion/guardado, solo verificando OUTPUT_PATH existente")
|
print("[INFO] --verify-only: saltando calibracion/guardado, solo verificando OUTPUT_PATH existente")
|
||||||
else:
|
else:
|
||||||
print(f"[INFO] TRAIN_DATA_PATH={TRAIN_DATA_PATH}")
|
print(f"[INFO] CALIBRATION_CACHE_PATH={CALIBRATION_CACHE_PATH}")
|
||||||
print(f"[INFO] NUM_CALIBRATION_SAMPLES={NUM_CALIBRATION_SAMPLES} MAX_SEQUENCE_LENGTH={MAX_SEQUENCE_LENGTH}")
|
|
||||||
print(f"[INFO] NUM_ULTRACHAT_SAMPLES={NUM_ULTRACHAT_SAMPLES}")
|
|
||||||
|
|
||||||
print(f"[INFO] cargando processor desde {MODEL_PATH}")
|
print(f"[INFO] cargando processor desde {MODEL_PATH}")
|
||||||
processor = AutoProcessor.from_pretrained(str(MODEL_PATH), trust_remote_code=True)
|
processor = AutoProcessor.from_pretrained(str(MODEL_PATH), trust_remote_code=True)
|
||||||
tokenizer = processor.tokenizer
|
tokenizer = processor.tokenizer
|
||||||
|
|
||||||
calibration_dataset = load_calibration_dataset(tokenizer)
|
if CALIBRATION_CACHE_PATH.exists():
|
||||||
|
print(f"[INFO] cargando muestra de calibracion YA PREPARADA desde {CALIBRATION_CACHE_PATH}")
|
||||||
|
calibration_dataset = Dataset.load_from_disk(str(CALIBRATION_CACHE_PATH))
|
||||||
|
print(f"[INFO] {len(calibration_dataset)} ejemplos cargados desde el cache (sin tocar datasets/red)")
|
||||||
|
else:
|
||||||
|
print(f"[INFO] TRAIN_DATA_PATH={TRAIN_DATA_PATH}")
|
||||||
|
print(f"[INFO] NUM_CALIBRATION_SAMPLES={NUM_CALIBRATION_SAMPLES} MAX_SEQUENCE_LENGTH={MAX_SEQUENCE_LENGTH}")
|
||||||
|
print(f"[INFO] NUM_ULTRACHAT_SAMPLES={NUM_ULTRACHAT_SAMPLES}")
|
||||||
|
print(
|
||||||
|
f"[INFO] no hay cache en {CALIBRATION_CACHE_PATH} -- construyendo la muestra de "
|
||||||
|
"calibracion en este mismo proceso (usar --prepare-calibration antes evita esto)"
|
||||||
|
)
|
||||||
|
calibration_dataset = load_calibration_dataset(tokenizer)
|
||||||
|
|
||||||
|
import gc
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
print(f"[INFO] cargando modelo desde {MODEL_PATH} (dtype=auto)")
|
print(f"[INFO] cargando modelo desde {MODEL_PATH} (dtype=auto)")
|
||||||
t_load = time.time()
|
t_load = time.time()
|
||||||
|
|||||||
Reference in New Issue
Block a user