cham-ocr-studio / ocr-training /scripts /run_l4_interactive.py
phucsd's picture
Upload folder using huggingface_hub
75b8dbb verified
Raw History Blame Contribute Delete
14.1 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
End-to-End Optimized Pipeline on NVIDIA L4 (1.68 cr/h - g6.4xlarge).
- Cost: 1.68 cr/h (53% cheaper than AWS L40S, cheaper than Nebius 2.14 cr/h)
- In-Place Interactive Pre-Flight Verification:
1. Train DataLoader (2 batches)
2. Eval DataLoader (5 batches, batch_size=1)
3. Forward pass + DBPostProcess + DetMetric
- Only launches 150 epochs training after 100% Pre-flight test passed.
- Model export + local download + guaranteed auto-stop when complete.
"""
import os
import sys
import time
import base64
import zipfile
from lightning_sdk import Studio, Machine
if sys.stdout and sys.stdout.encoding != 'utf-8':
try:
sys.stdout.reconfigure(encoding='utf-8')
except AttributeError:
pass
def log(msg):
timestamp = time.strftime("%H:%M:%S")
print(f"[{timestamp}] {msg}", flush=True)
def main():
log("=================================================================")
log("🚀 OPTION A: KHỞI CHẠY PIPELINE TRÊN NVIDIA L4 (1.68 cr/h - 24GB)")
log("=================================================================")
studio = Studio(name="cham-det-h100", teamspace="phucsd", org="phucsd-org")
log(f"Studio hiện tại: Status={studio.status} | Machine={studio.machine}")
target_machine = Machine.L4
log(f"\n🔥 [BƯỚC 1/6] Khởi động Studio với NVIDIA L4 (Machine.L4 - 1.68 cr/h)...")
if str(studio.status).lower() != "running":
studio.start(machine=target_machine)
else:
if str(studio.machine) != str(target_machine):
log(f"Chuyển máy từ {studio.machine} sang {target_machine}...")
studio.switch_machine(target_machine)
log(f"✅ Studio đang chạy! Machine: {studio.machine} | Status: {studio.status}")
# Giai đoạn Pre-flight: GIỮ MÁY MỞ nếu có lỗi để sửa ngay tại chỗ
log("\n🔍 [BƯỚC 2/6] Kiểm tra phần cứng GPU L4...")
gpu_info = studio.run("nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader").strip()
log(f"🔥 GPU: {gpu_info}")
log("📦 Kiểm tra PaddlePaddle GPU 3.3.1...")
paddle_check = studio.run("python3 -c \"import paddle; print('PADDLE_READY', paddle.__version__, paddle.device.cuda.device_count())\" || true").strip()
if "PADDLE_READY" not in paddle_check or " 0" in paddle_check:
log("Cài đặt paddlepaddle-gpu cu118...")
studio.run("pip install -q paddlepaddle-gpu -i https://www.paddlepaddle.org.cn/packages/stable/cu118/")
paddle_check = studio.run("python3 -c \"import paddle; print('PADDLE_READY', paddle.__version__, paddle.device.cuda.device_count())\"").strip()
log(f"✅ PaddlePaddle: {paddle_check}")
log("📦 Cập nhật gói phụ thuộc tương thích NumPy 2.x...")
studio.run("python3 -m pip install -q --upgrade 'opencv-python>=4.10' 'opencv-python-headless>=4.10' 'scipy>=1.13' rapidfuzz visualdl pyclipper shapely lmdb attrdict")
log("🔧 Thiết lập sitecustomize.py monkeypatch...")
sitecust_cmd = """python3 -c "
import site, pathlib
patch = '''import numpy as np
if not hasattr(np, 'sctypes'): np.sctypes = {'int': [np.int8, np.int16, np.int32, np.int64], 'uint': [np.uint8, np.uint16, np.uint32, np.uint64], 'float': [np.float16, np.float32, np.float64], 'complex': [np.complex64, np.complex128], 'others': [bool, object, bytes, str]}
if not hasattr(np, 'bool'): np.bool = bool
if not hasattr(np, 'int'): np.int = int
if not hasattr(np, 'float'): np.float = float
if not hasattr(np, 'typeDict'): np.typeDict = {}
if not hasattr(np, 'long'): np.long = int
if not hasattr(np, 'ulong'): np.ulong = int
if not hasattr(np, 'unicode_'): np.unicode_ = str
if not hasattr(np, 'string_'): np.string_ = bytes
if not hasattr(np, 'object_'): np.object_ = object
if not hasattr(np, 'complex_'): np.complex_ = complex
'''
sp_list = site.getsitepackages()
for sp in sp_list:
try:
(pathlib.Path(sp) / 'sitecustomize.py').write_text(patch, encoding='utf-8')
print(f'Wrote sitecustomize.py to {sp}')
except Exception as e:
pass
" """
studio.run(sitecust_cmd)
log("🔧 Vá random_crop_data.py loại bỏ size=1 cho NumPy 2.x...")
crop_patch_cmd = """python3 -c "
import pathlib
p = pathlib.Path('Cham-OCR/ocr-training/PaddleOCR/ppocr/data/imaug/random_crop_data.py')
if p.exists():
txt = p.read_text(encoding='utf-8')
if 'size=1' in txt:
txt = txt.replace('xx = int(np.random.choice(axis, size=1))', 'xx = int(np.random.choice(axis))')
p.write_text(txt, encoding='utf-8')
print('Đã vá random_crop_data.py')
else:
print('random_crop_data.py đã được vá trước đó')
" """
log(f" {studio.run(crop_patch_cmd).strip()}")
log("\n📄 [BƯỚC 3/6] Đồng bộ cấu hình ch_PP-OCRv4_det_h100.yml trực tiếp từ máy local...")
local_cfg_path = os.path.join(os.getcwd(), "ocr-training", "configs", "det", "ch_PP-OCRv4_det_h100.yml")
with open(local_cfg_path, "rb") as f:
b64_cfg = base64.b64encode(f.read()).decode('ascii')
studio.run(f"echo '{b64_cfg}' | base64 -d > Cham-OCR/ocr-training/PaddleOCR/configs/det/ch_PP-OCRv4_det_h100.yml")
studio.run(f"cp Cham-OCR/ocr-training/PaddleOCR/configs/det/ch_PP-OCRv4_det_h100.yml Cham-OCR/ocr-training/configs/det/ch_PP-OCRv4_det_h100.yml")
log(" CONFIG_SYNCED_SUCCESSFULLY via base64 pipe")
log("\n🧪 [BƯỚC 4/6] Chạy kiểm thử tại chỗ PRE-FLIGHT TEST (Train + Eval + Metric)...")
preflight_script = """cd Cham-OCR/ocr-training/PaddleOCR && python3 -c "
import logging
logger = logging.getLogger('preflight')
import yaml, paddle, time
from ppocr.data import build_dataloader
from ppocr.modeling.architectures import build_model
from ppocr.postprocess import build_post_process
from ppocr.metrics import build_metric
with open('configs/det/ch_PP-OCRv4_det_h100.yml', 'r') as f:
config = yaml.safe_load(f)
print('1. Kiểm tra Train DataLoader (batch size 32)...')
train_loader = build_dataloader(config, 'Train', paddle.device.get_device(), logger)
sample_train_batch = None
for idx, batch in enumerate(train_loader):
print(f' Train Batch {idx}: image shape = {batch[0].shape}')
sample_train_batch = batch
if idx >= 1:
break
print('2. Kiểm tra Eval DataLoader (batch size 1, dynamic shapes)...')
eval_loader = build_dataloader(config, 'Eval', paddle.device.get_device(), logger)
eval_batches = []
for idx, batch in enumerate(eval_loader):
print(f' Eval Batch {idx}: image shape = {batch[0].shape}, polys count = {len(batch[2][0])}')
eval_batches.append(batch)
if idx >= 4:
break
print('3. Kiểm tra Model forward pass + PostProcess + Metric...')
model = build_model(config['Architecture'])
model.eval()
post_process_class = build_post_process(config['PostProcess'])
eval_class = build_metric(config['Metric'])
with paddle.no_grad():
for batch in eval_batches:
images = batch[0]
preds = model(images)
post_res = post_process_class(preds, batch[1])
eval_class(post_res, batch)
metric = eval_class.get_metric()
print(f' Evaluation metric calculated successfully: {metric}')
print('=== PRE_FLIGHT_PASSED_100% ===')
" """
preflight_out = studio.run(preflight_script).strip()
log(f"Kết quả Pre-flight Test:\n{preflight_out}")
if "=== PRE_FLIGHT_PASSED_100% ===" not in preflight_out:
log("❌ Pre-flight test chưa hoàn thành trọn vẹn! Máy vẫn được GIỮ MỞ để kiểm tra.")
return
log("\n🎉 PRE-FLIGHT TEST ĐÃ ĐẠT 100%! BẮT ĐẦU CHẠY HUẤN LUYỆN CHÍNH THỨC 150 EPOCHS...")
try:
log("\n🚀 [BƯỚC 5/6] Khởi chạy huấn luyện Cham-DBNet (150 epochs, batch 32, single-GPU L4)...")
studio.run("pkill -9 -f train.py 2>/dev/null || true")
studio.run("pkill -9 -f python 2>/dev/null || true")
time.sleep(2)
studio.run("cd Cham-OCR/ocr-training/PaddleOCR && rm -f train.log && rm -rf log output/ch_PP-OCRv4_det_cham_h100")
studio.run("mkdir -p Cham-OCR/ocr-training/output")
time.sleep(1)
vram_check = studio.run("nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader").strip()
log(f" VRAM trước khi khởi chạy: {vram_check}")
launch_cmd = (
"cd Cham-OCR/ocr-training/PaddleOCR && "
"nohup python3 -u tools/train.py -c configs/det/ch_PP-OCRv4_det_h100.yml > train.log 2>&1 &"
)
studio.run(launch_cmd)
time.sleep(5)
log("✅ Tiến trình huấn luyện đã kích hoạt chạy nền! Đang bắt đầu theo dõi tiến độ...")
start_time = time.time()
prev_log = ""
while True:
time.sleep(20)
ps_cnt = studio.run("ps aux | grep '[t]ools/train.py' | wc -l").strip()
is_alive = int(ps_cnt or "0") > 0
try:
latest_lines = studio.run(
"cd Cham-OCR/ocr-training/PaddleOCR && tail -n 15 train.log"
).strip()
if latest_lines and latest_lines != prev_log:
prev_log = latest_lines
elapsed_min = (time.time() - start_time) / 60
log(f"⏱️ [{elapsed_min:.1f}m] Tiến độ log:")
matched = False
for l in latest_lines.splitlines():
if any(k in l for k in ["epoch:", "loss:", "hmean:", "save checkpoint", "Error", "Traceback", "MainIndicator", "step:", "eval model:", "PPLCNet", "download"]):
log(f" {l}")
matched = True
if not matched and latest_lines.strip():
log(f" {latest_lines.splitlines()[-1]}")
except Exception as e:
log(f"Log notice: {e}")
if not is_alive:
final_log = studio.run(
"cd Cham-OCR/ocr-training/PaddleOCR && tail -n 50 train.log"
).strip()
log(f"--- 50 DÒNG LOG CUỐI CÙNG ---\n{final_log}\n-----------------------------")
if "Traceback (most recent call last)" in final_log or "Exit code 1" in final_log or "SystemError:" in final_log or "FatalError:" in final_log:
raise RuntimeError("Tiến trình huấn luyện gặp lỗi ngoại lệ dừng đột ngột!")
chk_check = studio.run("test -f Cham-OCR/ocr-training/PaddleOCR/output/ch_PP-OCRv4_det_cham_h100/latest.pdparams && echo CHK_FOUND || echo NO_CHK").strip()
if "CHK_FOUND" not in chk_check:
raise RuntimeError("Huấn luyện dừng trước khi lưu checkpoint!")
log("\n🎉 TIẾN TRÌNH HUẤN LUYỆN 150 EPOCHS ĐÃ KẾT THÚC HOÀN TOÀN THÀNH CÔNG!")
break
log("\n📦 [BƯỚC 6/6] Xuất mô hình Inference & Tải về máy local...")
export_script = """
cd Cham-OCR/ocr-training/PaddleOCR
BEST_MODEL="output/ch_PP-OCRv4_det_cham_h100/best_accuracy"
if [ ! -f "${BEST_MODEL}.pdparams" ]; then
BEST_MODEL="output/ch_PP-OCRv4_det_cham_h100/latest"
fi
echo "Using checkpoint: $BEST_MODEL"
mkdir -p ../output
python3 tools/export_model.py \
-c configs/det/ch_PP-OCRv4_det_h100.yml \
-o Global.pretrained_model=${BEST_MODEL} \
Global.save_inference_dir=../output/ch_PP-OCRv4_det_cham_infer
cd ../output
rm -f ../cham_dbnet_v1_infer.zip
python3 -c "import shutil; shutil.make_archive('../cham_dbnet_v1_infer', 'zip', '.', 'ch_PP-OCRv4_det_cham_infer')"
ls -lh ../cham_dbnet_v1_infer.zip
"""
export_out = studio.run(export_script).strip()
log(f"Export log:\n{export_out}")
log("⬇️ Đang tải tệp cham_dbnet_v1_infer.zip về thư mục ocr-training/ ...")
local_dest = os.path.join(os.getcwd(), "ocr-training", "cham_dbnet_v1_infer.zip")
downloaded = False
try:
studio.download_file("Cham-OCR/ocr-training/cham_dbnet_v1_infer.zip", local_dest)
if os.path.exists(local_dest) and os.path.getsize(local_dest) > 1000:
downloaded = True
log(" Đã tải thành công qua native studio.download_file!")
except Exception as dl_err:
log(f" Native download gặp lỗi ({dl_err}), chuyển sang Base64 fallback...")
if not downloaded:
remote_b64 = studio.run(
"python3 -c \"import base64, pathlib; p = pathlib.Path('Cham-OCR/ocr-training/cham_dbnet_v1_infer.zip'); print(base64.b64encode(p.read_bytes()).decode('ascii'))\""
).strip()
with open(local_dest, "wb") as f:
f.write(base64.b64decode(remote_b64))
local_size_mb = os.path.getsize(local_dest) / (1024 * 1024)
log(f"🎉 ĐÃ TẢI THÀNH CÔNG: {local_dest} ({local_size_mb:.2f} MB)")
infer_extract_dir = os.path.join(os.getcwd(), "ocr-training", "cham_dbnet_v1_infer")
os.makedirs(infer_extract_dir, exist_ok=True)
with zipfile.ZipFile(local_dest, "r") as zf:
zf.extractall(infer_extract_dir)
log(f"📂 Đã giải nén mô hình suy luận vào: {infer_extract_dir}")
except Exception as exc:
log(f"❌ XẢY RA LỖI: {exc}")
raise exc
finally:
log("\n🛑 [AN TOÀN] Đang dừng Studio để bảo vệ số dư tài khoản...")
try:
if str(studio.status).lower() == "running":
studio.stop()
log(f"✅ Studio đã DỪNG (Status: {studio.status})! 0 credit bị tiêu tốn thêm.")
else:
log(f"✅ Studio hiện tại đã ở trạng thái {studio.status}, an toàn.")
except Exception as stop_err:
log(f"Cảnh báo khi dừng Studio: {stop_err}")
log("\n🎊 TOÀN BỘ QUY TRÌNH ĐÃ HOÀN TẤT THÀNH CÔNG RỰC RỠ!")
if __name__ == "__main__":
main()