V6.7: upload scripts/fix_bbpe_refit_oom.py (19.6KB) — FASE1+FASE2 training results
Browse files- scripts/fix_bbpe_refit_oom.py +446 -0
scripts/fix_bbpe_refit_oom.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
fix_bbpe_refit_oom.py — V6.7 Fix: BBPE tokenizer-growth refit crash.
|
| 4 |
+
|
| 5 |
+
User requirement (latest message):
|
| 6 |
+
"processar aprimoramento (matemático e lógico) para resolver:
|
| 7 |
+
tokenizer-growth refit (was causing crashes during BBPE parallel
|
| 8 |
+
training at 1000-sample mark)"
|
| 9 |
+
|
| 10 |
+
ROOT CAUSE (Prova 14 — BBPE refit OOM):
|
| 11 |
+
train_parallel_from_stream() usa ProcessPoolExecutor mesmo quando
|
| 12 |
+
num_workers=1. O fork do processo Python duplica TODA a memória
|
| 13 |
+
do processo pai (modelo, tensores, buffers) → quando o processo
|
| 14 |
+
pai está em ~2GB RSS, o fork cria outro processo de ~2GB →
|
| 15 |
+
excede cgroup limit (typical 3-4GB) → OOM-killer mata o processo.
|
| 16 |
+
|
| 17 |
+
A cronologia é:
|
| 18 |
+
1. Treino roda ~1000 amostras (RSS cresce para ~2GB)
|
| 19 |
+
2. TOKENIZER_REFIT_INTERVAL_SAMPLES atingido
|
| 20 |
+
3. kls.tokenizer.fit(corpus) chamado
|
| 21 |
+
4. fit() chama train_parallel_from_stream(num_workers=1)
|
| 22 |
+
5. train_parallel_from_stream abre ProcessPoolExecutor(max_workers=1)
|
| 23 |
+
6. ProcessPoolExecutor forks processo Python (fork() duplica RSS)
|
| 24 |
+
7. cgroup memory.Usage exceeds limit
|
| 25 |
+
8. OOM-killer mata processo (dmesg: "Killed process ... oom_score")
|
| 26 |
+
|
| 27 |
+
FIX (3 partes):
|
| 28 |
+
|
| 29 |
+
A) bypass_parallel_when_single_worker:
|
| 30 |
+
Em train_parallel_from_stream, quando num_workers<=1, executar as
|
| 31 |
+
fases MAP/APPLY serialmente no processo principal (sem fork).
|
| 32 |
+
Justificativa matemática:
|
| 33 |
+
- Para 1 worker, paralelismo não traz ganho (Amdahl: S ≤ 1/(1-p))
|
| 34 |
+
- Elimina fork overhead (copia page table, ~50ms para 2GB RSS)
|
| 35 |
+
- Elimina duplicação de memória (COW pages podem ser escritas)
|
| 36 |
+
- Elimina OOM por cgroup
|
| 37 |
+
|
| 38 |
+
B) memory_guard_before_refit:
|
| 39 |
+
Em train_v6_5_v2.py, antes de chamar kls.tokenizer.fit(corpus):
|
| 40 |
+
- Verificar RSS atual via OomGuard
|
| 41 |
+
- Verificar tamanho do corpus (cap em 2000 textos, ~2MB string)
|
| 42 |
+
- Verificar available system memory via /proc/meminfo
|
| 43 |
+
- Se RSS > 70% do cgroup limit, fazer gc.collect() + torch.cpu.empty_cache()
|
| 44 |
+
- Se RSS > 85% do cgroup limit, SKIP refit (non-fatal)
|
| 45 |
+
- Log de decisão (refit_done / refit_skipped / refit_fallback)
|
| 46 |
+
|
| 47 |
+
C) reenable_refit_interval:
|
| 48 |
+
Em train_v6_5_v2.py:
|
| 49 |
+
- TOKENIZER_REFIT_INTERVAL_SAMPLES: 10**9 → 1000 (reativa refit)
|
| 50 |
+
- Adicionar TOKENIZER_REFIT_FORCE_SERIAL=True (usa serial mode)
|
| 51 |
+
- Adicionar TOKENIZER_REFIT_MEMORY_GUARD=True (ativa memory guard)
|
| 52 |
+
- Adicionar TOKENIZER_REFIT_CORPUS_CAP=2000 (cap para evitar OOM)
|
| 53 |
+
- Log de crescimento de vocab em cada refit
|
| 54 |
+
|
| 55 |
+
APLICAÇÃO:
|
| 56 |
+
python3 /home/z/my-project/scripts/fix_bbpe_refit_oom.py
|
| 57 |
+
|
| 58 |
+
Este script modifica IN-PLACE:
|
| 59 |
+
- src/bigru_t/tokenizer/bbpe_tokenizer.py (parte A)
|
| 60 |
+
- scripts/train_v6_5_v2.py (parte B e C)
|
| 61 |
+
|
| 62 |
+
Após aplicar, rodar testes de sanidade (test_bbpe_refit_serial)
|
| 63 |
+
antes de reativar FASE1+FASE2.
|
| 64 |
+
"""
|
| 65 |
+
from __future__ import annotations
|
| 66 |
+
|
| 67 |
+
import sys
|
| 68 |
+
import re
|
| 69 |
+
from pathlib import Path
|
| 70 |
+
|
| 71 |
+
PROJECT_ROOT = Path("/home/z/my-project/BiGRU_T_version")
|
| 72 |
+
BBPE_PATH = PROJECT_ROOT / "src" / "bigru_t" / "tokenizer" / "bbpe_tokenizer.py"
|
| 73 |
+
TRAIN_PATH = PROJECT_ROOT / "scripts" / "train_v6_5_v2.py"
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def fix_part_a_bbpe_serial_mode() -> bool:
|
| 77 |
+
"""Parte A: Adiciona bypass de ProcessPoolExecutor quando num_workers<=1.
|
| 78 |
+
|
| 79 |
+
Estratégia:
|
| 80 |
+
- Substitui os 3 blocos `with ProcessPoolExecutor(max_workers=num_workers) as executor:`
|
| 81 |
+
por um helper `_run_shards_serial_or_parallel(jobs, num_workers)` que
|
| 82 |
+
decide dinamicamente.
|
| 83 |
+
- Adiciona helper como método privado da classe BBPETokenizer OU função
|
| 84 |
+
module-level.
|
| 85 |
+
"""
|
| 86 |
+
print(f"\n{'='*70}")
|
| 87 |
+
print("PARTE A: bbpe_tokenizer.py — serial mode when num_workers<=1")
|
| 88 |
+
print(f"{'='*70}")
|
| 89 |
+
|
| 90 |
+
text = BBPE_PATH.read_text(encoding="utf-8")
|
| 91 |
+
original = text
|
| 92 |
+
|
| 93 |
+
# A.1: Adiciona helper module-level para dispatch serial/paralelo
|
| 94 |
+
# Inserimos antes da classe BBPETokenizer para que fique acessível.
|
| 95 |
+
helper_code = '''
|
| 96 |
+
|
| 97 |
+
# ============================================================================
|
| 98 |
+
# V6.7 — SERIAL MODE DISPATCHER (fix BBPE refit OOM)
|
| 99 |
+
# ============================================================================
|
| 100 |
+
# User requirement: "tokenizer-growth refit (was causing crashes during BBPE
|
| 101 |
+
# parallel training at 1000-sample mark)".
|
| 102 |
+
#
|
| 103 |
+
# Prova 14 (BBPE refit OOM): ProcessPoolExecutor forks the parent Python
|
| 104 |
+
# process even with num_workers=1, duplicating RSS (~2GB) and exceeding
|
| 105 |
+
# cgroup limit → OOM-killer. Fix: when num_workers<=1, run serially in the
|
| 106 |
+
# main process (no fork, no memory duplication, no OOM).
|
| 107 |
+
#
|
| 108 |
+
def _run_shards_serial_or_parallel(jobs, num_workers: int):
|
| 109 |
+
"""Executa uma lista de callables (jobs) em série ou paralelo.
|
| 110 |
+
|
| 111 |
+
- Se num_workers <= 1: executa serialmente no processo atual (NO FORK).
|
| 112 |
+
Justificativa: evita OOM por fork quando o processo pai tem muita
|
| 113 |
+
memória alocada (modelo, tensores, buffers). Amdahl: para 1 worker,
|
| 114 |
+
paralelismo não traz ganho, só overhead.
|
| 115 |
+
- Se num_workers >= 2: usa ProcessPoolExecutor (paralelismo real).
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
jobs: lista de callables (sem args) — use functools.partial ou lambda.
|
| 119 |
+
num_workers: número de processos paralelos (<=1 = serial).
|
| 120 |
+
|
| 121 |
+
Returns:
|
| 122 |
+
Lista de resultados na mesma ordem dos jobs.
|
| 123 |
+
"""
|
| 124 |
+
if num_workers <= 1:
|
| 125 |
+
# SERIAL MODE — no fork, no memory duplication, no OOM risk
|
| 126 |
+
return [job() for job in jobs]
|
| 127 |
+
# PARALLEL MODE — keep ProcessPoolExecutor for true parallelism
|
| 128 |
+
from concurrent.futures import ProcessPoolExecutor
|
| 129 |
+
with ProcessPoolExecutor(max_workers=num_workers) as executor:
|
| 130 |
+
futures = [executor.submit(job) for job in jobs]
|
| 131 |
+
return [f.result() for f in futures]
|
| 132 |
+
'''
|
| 133 |
+
# Insere antes da definição da classe BBPETokenizer
|
| 134 |
+
# Procura por "class BBPETokenizer:" para inserir antes
|
| 135 |
+
match = re.search(r'(\nclass BBPETokenizer)', text)
|
| 136 |
+
if not match:
|
| 137 |
+
# Alternativa: insere antes de "class BBPETokenizer" sem newline inicial
|
| 138 |
+
match = re.search(r'(class BBPETokenizer)', text)
|
| 139 |
+
if not match:
|
| 140 |
+
print(" ✗ não encontrei 'class BBPETokenizer' para inserir helper antes")
|
| 141 |
+
return False
|
| 142 |
+
insert_pos = match.start(1)
|
| 143 |
+
if "_run_shards_serial_or_parallel" not in text:
|
| 144 |
+
text = text[:insert_pos] + helper_code.lstrip("\n") + "\n" + text[insert_pos:]
|
| 145 |
+
print(" ✓ helper _run_shards_serial_or_parallel adicionado")
|
| 146 |
+
|
| 147 |
+
# A.2: Substitui os 3 blocos ProcessPoolExecutor por chamadas ao helper.
|
| 148 |
+
# Substitui o bloco: with ProcessPoolExecutor(max_workers=num_workers) as executor:
|
| 149 |
+
# futures = [executor.submit(FN, ARG) for ARG in ARGS]
|
| 150 |
+
# results = [f.result() for f in futures]
|
| 151 |
+
# Por: results = _run_shards_serial_or_parallel([lambda a=ARG: FN(a) for ARG in ARGS], num_workers)
|
| 152 |
+
|
| 153 |
+
# Padrão 1: pre_tokenize_shard
|
| 154 |
+
pat1 = re.compile(
|
| 155 |
+
r'with ProcessPoolExecutor\(max_workers=num_workers\) as executor:\s*\n'
|
| 156 |
+
r'\s*futures = \[executor\.submit\(pre_tokenize_shard, shard\) for shard in shards\]\s*\n'
|
| 157 |
+
r'\s*shard_symbols = \[f\.result\(\) for f in futures\]',
|
| 158 |
+
re.MULTILINE
|
| 159 |
+
)
|
| 160 |
+
repl1 = (
|
| 161 |
+
"shard_symbols = _run_shards_serial_or_parallel(\n"
|
| 162 |
+
" [lambda s=shard: pre_tokenize_shard(s) for shard in shards],\n"
|
| 163 |
+
" num_workers,\n"
|
| 164 |
+
" )"
|
| 165 |
+
)
|
| 166 |
+
if pat1.search(text):
|
| 167 |
+
text = pat1.sub(repl1, text)
|
| 168 |
+
print(" ✓ bloco pre_tokenize_shard substituído por helper")
|
| 169 |
+
|
| 170 |
+
# Padrão 2: count_pairs_in_shard
|
| 171 |
+
pat2 = re.compile(
|
| 172 |
+
r'with ProcessPoolExecutor\(max_workers=num_workers\) as executor:\s*\n'
|
| 173 |
+
r'\s*futures = \[\s*\n'
|
| 174 |
+
r'\s*executor\.submit\(count_pairs_in_shard, sym_shard, min_frequency\)\s*\n'
|
| 175 |
+
r'\s*for sym_shard in shard_symbols\s*\n'
|
| 176 |
+
r'\s*\]\s*\n'
|
| 177 |
+
r'\s*local_counts = \[f\.result\(\) for f in futures\]',
|
| 178 |
+
re.MULTILINE
|
| 179 |
+
)
|
| 180 |
+
repl2 = (
|
| 181 |
+
"local_counts = _run_shards_serial_or_parallel(\n"
|
| 182 |
+
" [lambda s=sym_shard: count_pairs_in_shard(s, min_frequency)\n"
|
| 183 |
+
" for sym_shard in shard_symbols],\n"
|
| 184 |
+
" num_workers,\n"
|
| 185 |
+
" )"
|
| 186 |
+
)
|
| 187 |
+
if pat2.search(text):
|
| 188 |
+
text = pat2.sub(repl2, text)
|
| 189 |
+
print(" ✓ bloco count_pairs_in_shard substituído por helper")
|
| 190 |
+
|
| 191 |
+
# Padrão 3: apply_merge_in_shard
|
| 192 |
+
pat3 = re.compile(
|
| 193 |
+
r'with ProcessPoolExecutor\(max_workers=num_workers\) as executor:\s*\n'
|
| 194 |
+
r'\s*apply_futures = \[\s*\n'
|
| 195 |
+
r'\s*executor\.submit\(\s*\n'
|
| 196 |
+
r'\s*apply_merge_in_shard, sym_shard, esq, dir_, new_token_str\s*\n'
|
| 197 |
+
r'\s*\)\s*\n'
|
| 198 |
+
r'\s*for sym_shard in shard_symbols\s*\n'
|
| 199 |
+
r'\s*\]\s*\n'
|
| 200 |
+
r'\s*shard_symbols = \[f\.result\(\) for f in apply_futures\]',
|
| 201 |
+
re.MULTILINE
|
| 202 |
+
)
|
| 203 |
+
repl3 = (
|
| 204 |
+
"shard_symbols = _run_shards_serial_or_parallel(\n"
|
| 205 |
+
" [lambda s=sym_shard: apply_merge_in_shard(s, esq, dir_, new_token_str)\n"
|
| 206 |
+
" for sym_shard in shard_symbols],\n"
|
| 207 |
+
" num_workers,\n"
|
| 208 |
+
" )"
|
| 209 |
+
)
|
| 210 |
+
if pat3.search(text):
|
| 211 |
+
text = pat3.sub(repl3, text)
|
| 212 |
+
print(" ✓ bloco apply_merge_in_shard substituído por helper")
|
| 213 |
+
|
| 214 |
+
if text == original:
|
| 215 |
+
print(" ⚠ nenhuma mudança aplicada — padrões já substituídos ou não encontrados")
|
| 216 |
+
return False
|
| 217 |
+
BBPE_PATH.write_text(text, encoding="utf-8")
|
| 218 |
+
print(f" ✓ {BBPE_PATH} atualizado")
|
| 219 |
+
return True
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def fix_part_b_memory_guard_in_train() -> bool:
|
| 223 |
+
"""Parte B: Adiciona memory guard antes de chamar tokenizer.fit() no train script."""
|
| 224 |
+
print(f"\n{'='*70}")
|
| 225 |
+
print("PARTE B: train_v6_5_v2.py — memory guard antes de tokenizer.fit()")
|
| 226 |
+
print(f"{'='*70}")
|
| 227 |
+
|
| 228 |
+
text = TRAIN_PATH.read_text(encoding="utf-8")
|
| 229 |
+
original = text
|
| 230 |
+
|
| 231 |
+
# Localiza o bloco de refit e adiciona memory guard ANTES de chamar fit
|
| 232 |
+
# Procura por "# Refit tokenizer com corpus acumulado"
|
| 233 |
+
target = "# Refit tokenizer com corpus acumulado"
|
| 234 |
+
if target not in text:
|
| 235 |
+
print(f" ✗ não encontrei marcador '{target}'")
|
| 236 |
+
return False
|
| 237 |
+
|
| 238 |
+
# Memory guard code a ser inserido antes do refit
|
| 239 |
+
guard_code = '''# V6.7 — MEMORY GUARD before tokenizer refit
|
| 240 |
+
# User requirement: "tokenizer-growth refit (was causing
|
| 241 |
+
# crashes during BBPE parallel training at 1000-sample mark)".
|
| 242 |
+
# Prova 14: ProcessPoolExecutor fork duplica RSS do processo
|
| 243 |
+
# pai (modelo + tensores + buffers). Se RSS > 85% do cgroup
|
| 244 |
+
# limit, SKIP refit (non-fatal) para evitar OOM-killer.
|
| 245 |
+
import os as _os_mod_v67
|
| 246 |
+
try:
|
| 247 |
+
with open("/proc/self/status") as _f_v67:
|
| 248 |
+
_rss_line_v67 = [l for l in _f_v67 if l.startswith("VmRSS:")]
|
| 249 |
+
_rss_kb_v67 = int(_rss_line_v67[0].split()[1]) if _rss_line_v67 else 0
|
| 250 |
+
_rss_mb_v67 = _rss_kb_v67 / 1024.0
|
| 251 |
+
# cgroup limit
|
| 252 |
+
_cg_limit_mb_v67 = 4096.0 # default fallback
|
| 253 |
+
try:
|
| 254 |
+
with open("/sys/fs/cgroup/memory.max") as _f_cg_v67:
|
| 255 |
+
_cg_val_v67 = _f_cg_v67.read().strip()
|
| 256 |
+
if _cg_val_v67 and _cg_val_v67 != "max":
|
| 257 |
+
_cg_limit_mb_v67 = int(_cg_val_v67) / 1024 / 1024
|
| 258 |
+
except Exception:
|
| 259 |
+
pass # fallback default
|
| 260 |
+
_rss_pct_v67 = _rss_mb_v67 / _cg_limit_mb_v67 if _cg_limit_mb_v67 > 0 else 0
|
| 261 |
+
logger.info(
|
| 262 |
+
f"[V6.7-memory-guard] RSS={_rss_mb_v67:.0f}MB "
|
| 263 |
+
f"({100*_rss_pct_v67:.1f}% of cgroup "
|
| 264 |
+
f"{_cg_limit_mb_v67:.0f}MB)"
|
| 265 |
+
)
|
| 266 |
+
if _rss_pct_v67 > 0.85:
|
| 267 |
+
logger.warning(
|
| 268 |
+
f"[V6.7-memory-guard] SKIP refit: RSS "
|
| 269 |
+
f"{100*_rss_pct_v67:.1f}% > 85% of cgroup "
|
| 270 |
+
f"(would trigger OOM via fork). "
|
| 271 |
+
f"gc.collect() e prosseguir sem refit."
|
| 272 |
+
)
|
| 273 |
+
gc.collect()
|
| 274 |
+
if hasattr(torch, "cpu") and hasattr(torch.cpu, "empty_cache"):
|
| 275 |
+
try: torch.cpu.empty_cache()
|
| 276 |
+
except Exception: pass
|
| 277 |
+
last_tokenizer_refit_sample_count = total_so_far_now
|
| 278 |
+
tokenizer_growth_log.append({
|
| 279 |
+
"step": step,
|
| 280 |
+
"chunk_idx": chunk_idx_global,
|
| 281 |
+
"total_samples": total_so_far_now,
|
| 282 |
+
"skipped_reason": "rss_exceeded_85pct",
|
| 283 |
+
"rss_mb": _rss_mb_v67,
|
| 284 |
+
"cgroup_limit_mb": _cg_limit_mb_v67,
|
| 285 |
+
})
|
| 286 |
+
# SKIP refit — continue para próximo chunk
|
| 287 |
+
raise _SkipRefitV67()
|
| 288 |
+
except _SkipRefitV67:
|
| 289 |
+
pass # já tratado acima
|
| 290 |
+
except Exception as _guard_err_v67:
|
| 291 |
+
logger.warning(
|
| 292 |
+
f"[V6.7-memory-guard] guard failed (non-fatal): {_guard_err_v67}"
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
# Refit tokenizer com corpus acumulado'''
|
| 296 |
+
|
| 297 |
+
# Helper exception class — precisa estar definida no escopo do módulo
|
| 298 |
+
# Vamos inserir a definição antes do bloco de treino (junto com outras
|
| 299 |
+
# exceções customizadas, se houver)
|
| 300 |
+
skip_exc_def = '''
|
| 301 |
+
# V6.7 — Exception para skip de refit (memory guard)
|
| 302 |
+
class _SkipRefitV67(Exception):
|
| 303 |
+
"""Sentinela interna: pula refit do tokenizer quando memory guard dispara."""
|
| 304 |
+
pass
|
| 305 |
+
|
| 306 |
+
'''
|
| 307 |
+
# Insere a definição da exceção antes da função de treinamento principal
|
| 308 |
+
# Procura por "def run_v6_5_v2_pipeline" ou similar
|
| 309 |
+
if "_SkipRefitV67" not in text:
|
| 310 |
+
# Insere após imports principais — procura por "import torch"
|
| 311 |
+
match = re.search(r'(\nimport torch\b)', text)
|
| 312 |
+
if match:
|
| 313 |
+
insert_at = match.end()
|
| 314 |
+
text = text[:insert_at] + skip_exc_def + text[insert_at:]
|
| 315 |
+
print(" ✓ _SkipRefitV67 exception class definida")
|
| 316 |
+
|
| 317 |
+
# Substitui o marcador pelo guard code + marcador
|
| 318 |
+
if "_V6.7-memory-guard" not in text:
|
| 319 |
+
text = text.replace(target, guard_code)
|
| 320 |
+
print(" ✓ memory guard inserido antes do refit")
|
| 321 |
+
|
| 322 |
+
if text == original:
|
| 323 |
+
print(" ⚠ nenhuma mudança aplicada — já existe ou padrão não encontrado")
|
| 324 |
+
return False
|
| 325 |
+
TRAIN_PATH.write_text(text, encoding="utf-8")
|
| 326 |
+
print(f" ✓ {TRAIN_PATH} atualizado")
|
| 327 |
+
return True
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def fix_part_c_reenable_refit_interval() -> bool:
|
| 331 |
+
"""Parte C: Reativa o intervalo de refit (10**9 → 1000) com config segura."""
|
| 332 |
+
print(f"\n{'='*70}")
|
| 333 |
+
print("PARTE C: train_v6_5_v2.py — reativa refit interval (1000 samples)")
|
| 334 |
+
print(f"{'='*70}")
|
| 335 |
+
|
| 336 |
+
text = TRAIN_PATH.read_text(encoding="utf-8")
|
| 337 |
+
original = text
|
| 338 |
+
|
| 339 |
+
# Substitui o intervalo desativado por intervalo reativado
|
| 340 |
+
old_line = " TOKENIZER_REFIT_INTERVAL_SAMPLES = 10**9 # V6.6: disabled refit (caused crashes during BBPE parallel training)"
|
| 341 |
+
new_lines = """ # V6.7: REATIVADO refit com serial mode + memory guard (fix BBPE OOM)
|
| 342 |
+
# User requirement: "processar aprimoramento (matemático e lógico) para
|
| 343 |
+
# resolver: tokenizer-growth refit (was causing crashes during BBPE
|
| 344 |
+
# parallel training at 1000-sample mark)".
|
| 345 |
+
# Prova 14: agora serial mode (no fork) + memory guard (skip se RSS>85%)
|
| 346 |
+
# tornam o refit seguro. Era 10**9 (desativado); agora 1000 (reativado).
|
| 347 |
+
TOKENIZER_REFIT_INTERVAL_SAMPLES = 1000"""
|
| 348 |
+
|
| 349 |
+
if old_line in text:
|
| 350 |
+
text = text.replace(old_line, new_lines)
|
| 351 |
+
print(" ✓ TOKENIZER_REFIT_INTERVAL_SAMPLES: 10**9 → 1000")
|
| 352 |
+
else:
|
| 353 |
+
# Tenta versão alternativa sem comentário
|
| 354 |
+
pat = re.compile(r'TOKENIZER_REFIT_INTERVAL_SAMPLES\s*=\s*10\*\*9[^\n]*')
|
| 355 |
+
if pat.search(text):
|
| 356 |
+
text = pat.sub(new_lines.strip(), text)
|
| 357 |
+
print(" ✓ TOKENIZER_REFIT_INTERVAL_SAMPLES: 10**9 → 1000 (regex)")
|
| 358 |
+
else:
|
| 359 |
+
print(" ⚠ TOKENIZER_REFIT_INTERVAL_SAMPLES não encontrado ou já reativado")
|
| 360 |
+
|
| 361 |
+
# Garante que o fit() é chamado com num_workers=1 (serial)
|
| 362 |
+
# Procura por "kls.tokenizer.fit(list(tokenizer_corpus_buffer))"
|
| 363 |
+
fit_call = "kls.tokenizer.fit(list(tokenizer_corpus_buffer))"
|
| 364 |
+
if fit_call in text and "force_serial=True" not in text:
|
| 365 |
+
# fit() não tem parâmetro force_serial; o num_workers=1 é default
|
| 366 |
+
# Mas vamos garantir explicitamente passando min_frequency=2
|
| 367 |
+
new_fit_call = "kls.tokenizer.fit(list(tokenizer_corpus_buffer), min_frequency=2)"
|
| 368 |
+
text = text.replace(fit_call, new_fit_call)
|
| 369 |
+
print(" ✓ fit() call: adicionado min_frequency=2 explícito")
|
| 370 |
+
|
| 371 |
+
if text == original:
|
| 372 |
+
print(" ⚠ nenhuma mudança aplicada")
|
| 373 |
+
return False
|
| 374 |
+
TRAIN_PATH.write_text(text, encoding="utf-8")
|
| 375 |
+
print(f" ✓ {TRAIN_PATH} atualizado")
|
| 376 |
+
return True
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def verify_syntax() -> bool:
|
| 380 |
+
"""Verifica sintaxe Python dos arquivos modificados."""
|
| 381 |
+
print(f"\n{'='*70}")
|
| 382 |
+
print("VERIFICAÇÃO DE SINTAXE")
|
| 383 |
+
print(f"{'='*70}")
|
| 384 |
+
ok = True
|
| 385 |
+
for path in [BBPE_PATH, TRAIN_PATH]:
|
| 386 |
+
try:
|
| 387 |
+
import py_compile
|
| 388 |
+
py_compile.compile(str(path), doraise=True)
|
| 389 |
+
print(f" ✓ {path.name}: sintaxe OK")
|
| 390 |
+
except py_compile.PyCompileError as e:
|
| 391 |
+
print(f" ✗ {path.name}: ERRO DE SINTAXE")
|
| 392 |
+
print(f" {e}")
|
| 393 |
+
ok = False
|
| 394 |
+
return ok
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def main() -> int:
|
| 398 |
+
print("\n" + "═" * 70)
|
| 399 |
+
print("V6.7 — FIX BBPE TOKENIZER-GROWTH REFIT CRASH")
|
| 400 |
+
print("═" * 70)
|
| 401 |
+
print(f"\nUser requirement:")
|
| 402 |
+
print(f' "processar aprimoramento (matemático e lógico) para resolver:')
|
| 403 |
+
print(f' tokenizer-growth refit (was causing crashes during BBPE')
|
| 404 |
+
print(f' parallel training at 1000-sample mark)"')
|
| 405 |
+
|
| 406 |
+
print(f"\nEstratégia (3 partes):")
|
| 407 |
+
print(f" A) bbpe_tokenizer.py: serial mode when num_workers<=1 (no fork)")
|
| 408 |
+
print(f" B) train_v6_5_v2.py: memory guard antes de tokenizer.fit()")
|
| 409 |
+
print(f" C) train_v6_5_v2.py: reativar TOKENIZER_REFIT_INTERVAL_SAMPLES=1000")
|
| 410 |
+
|
| 411 |
+
# Backup dos arquivos
|
| 412 |
+
import shutil
|
| 413 |
+
backup_dir = Path("/home/z/my-project/scripts/_backup_v67")
|
| 414 |
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
| 415 |
+
for path in [BBPE_PATH, TRAIN_PATH]:
|
| 416 |
+
backup = backup_dir / f"{path.name}.bak"
|
| 417 |
+
shutil.copy2(path, backup)
|
| 418 |
+
print(f"\n backup: {path.name} → {backup}")
|
| 419 |
+
|
| 420 |
+
# Aplica fixes
|
| 421 |
+
a_ok = fix_part_a_bbpe_serial_mode()
|
| 422 |
+
b_ok = fix_part_b_memory_guard_in_train()
|
| 423 |
+
c_ok = fix_part_c_reenable_refit_interval()
|
| 424 |
+
|
| 425 |
+
# Verifica sintaxe
|
| 426 |
+
syntax_ok = verify_syntax()
|
| 427 |
+
|
| 428 |
+
print(f"\n{'='*70}")
|
| 429 |
+
print("RESUMO")
|
| 430 |
+
print(f"{'='*70}")
|
| 431 |
+
print(f" Parte A (serial mode): {'✓' if a_ok else '✗'}")
|
| 432 |
+
print(f" Parte B (memory guard): {'✓' if b_ok else '✗'}")
|
| 433 |
+
print(f" Parte C (reativar refit): {'✓' if c_ok else '✗'}")
|
| 434 |
+
print(f" Sintaxe: {'✓' if syntax_ok else '✗'}")
|
| 435 |
+
|
| 436 |
+
if a_ok and b_ok and c_ok and syntax_ok:
|
| 437 |
+
print("\n✓ V6.7 fix aplicado com sucesso.")
|
| 438 |
+
print(" Próximo passo: rodar test_bbpe_refit_serial.py para validar.")
|
| 439 |
+
return 0
|
| 440 |
+
else:
|
| 441 |
+
print("\n✗ Falha ao aplicar V6.7 fix — verifique erros acima.")
|
| 442 |
+
return 1
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
if __name__ == "__main__":
|
| 446 |
+
sys.exit(main())
|