Spaces:
Running
Running
Download ocr-training/scripts/run_l4_interactive.py from phucsd/cham-ocr-studio: direct link, hf CLI and curl.
- Browser
- Download file 14.1 kB
-
https://huggingface.co/spaces/phucsd/cham-ocr-studio/resolve/main/ocr-training/scripts/run_l4_interactive.py
- Command line
-
hf download hf://spaces/phucsd/cham-ocr-studio/ocr-training/scripts/run_l4_interactive.py
-
curl -L -o run_l4_interactive.py https://huggingface.co/spaces/phucsd/cham-ocr-studio/resolve/main/ocr-training/scripts/run_l4_interactive.py
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() | |