File size: 5,836 Bytes
9d614f1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | #!/usr/bin/env python3
"""RSS AI Engine - application entry point.
python RSS_AI_Engine/main.py --mode clean stream + clean data only
python RSS_AI_Engine/main.py --mode pretrain Stage-1 continual pre-training only
python RSS_AI_Engine/main.py --mode finetune Stage-2 rational SFT only
python RSS_AI_Engine/main.py --mode train_all purge -> fresh HF model -> clean -> pretrain -> finetune -> upload
python RSS_AI_Engine/main.py --mode serve load the trained model -> Telegram bot or CLI chat
Extras: --fresh-model --upload-only --skip-upload --limit 150|full --tier 1|2|3 --threads N
"""
from __future__ import annotations
import argparse
import importlib.util
import os
import signal
import subprocess
import sys
from datetime import datetime
from pathlib import Path
# Set native tokenizer/OpenMP limits BEFORE importing torch/transformers/tokenizers.
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["OMP_NUM_THREADS"] = "2"
os.environ["MKL_NUM_THREADS"] = "2"
ROOT = Path(__file__).resolve().parent
ENV_TEMPLATE = """HF_TOKEN="hf_PUT_YOUR_TOKEN_HERE"
TELEGRAM_BOT_TOKEN=""
TELEGRAM_CHAT_ID=""
BASE_MODEL_REPO="Qwen/Qwen2.5-0.5B-Instruct"
OUTPUT_HF_REPO="Biraboro/rss-ai-trained"
SOURCE_REPOS='[{"repo": "Biraboro/Youtop-trenscprit", "limit": 150}]'
LOCAL_CHAT_FILE="./chat.jsonl"
JSON_PRETRAIN_QUOTA="0.15"
PRETRAIN_CONV_RATIO="0.25"
PURGE_OLD_MODEL_ON_START="true"
EXTRA_BLOCKED_EXTENSIONS=""
"""
# (import name, pip name)
REQUIRED = [("torch", "torch"), ("transformers", "transformers"), ("accelerate", "accelerate"),
("peft", "peft"), ("datasets", "datasets"), ("huggingface_hub", "huggingface_hub"),
("safetensors", "safetensors"), ("psutil", "psutil"), ("dotenv", "python-dotenv"),
("requests", "requests")]
def ensure_env_file() -> None:
env = ROOT / ".env"
if not env.exists():
env.write_text(ENV_TEMPLATE, encoding="utf-8")
print(f"[Setup] created {env} - put your HF_TOKEN (write access) in it before uploading")
def _pip(package: str) -> None:
base = [sys.executable, "-m", "pip", "install", "-q", package]
if subprocess.call(base) != 0:
# PEP 668 "externally managed" interpreters (Debian/Ubuntu system python) need this flag
subprocess.check_call(base + ["--break-system-packages"])
def _cuda_present() -> bool:
try:
import torch
return bool(torch.cuda.is_available())
except Exception:
return False
def ensure_dependencies() -> None:
missing = [pip for mod, pip in REQUIRED if importlib.util.find_spec(mod) is None]
for package in missing:
print(f"[Setup] installing {package} ...")
_pip(package)
if _cuda_present() and importlib.util.find_spec("bitsandbytes") is None:
print("[Setup] CUDA detected -> installing bitsandbytes (4-bit NF4)")
try:
_pip("bitsandbytes")
except Exception as exc:
print(f"[Setup] bitsandbytes install failed ({exc}); GPU tier will run without 4-bit")
if not missing:
print("[Setup] all dependencies present")
def parse_args(argv=None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="RSS AI Engine", formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("--mode", choices=["clean", "pretrain", "finetune", "train_all", "serve"], default="serve",
help="clean | pretrain | finetune | train_all | serve (default: serve)")
parser.add_argument("--fresh-model", action="store_true",
help="purge old checkpoints/offload files and download the base model again")
parser.add_argument("--upload-only", action="store_true", help="only upload ./trained_model to OUTPUT_HF_REPO")
parser.add_argument("--skip-upload", action="store_true", help="train_all without the final upload")
parser.add_argument("--limit", default=None, help="override every repo limit: a number or 'full'")
parser.add_argument("--tier", type=int, choices=[1, 2, 3], default=None, help="force execution tier")
parser.add_argument("--threads", type=int, default=None, help="CPU threads for torch")
parser.add_argument("--no-install", action="store_true", help="do not auto-install missing dependencies")
return parser.parse_args(argv)
def _handle_sigterm(signum, _frame) -> None:
stamp = datetime.now().astimezone().isoformat(timespec="seconds")
print(f"\n[Runtime] received SIGTERM ({signum}) at {stamp}; "
"external supervisor/timeout or user termination is possible; sender is not identifiable here.",
flush=True)
raise SystemExit(128 + int(signum))
def main(argv=None) -> int:
try:
signal.signal(signal.SIGTERM, _handle_sigterm)
except (AttributeError, ValueError):
pass
args = parse_args(argv)
ensure_env_file()
if not args.no_install:
ensure_dependencies()
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
try:
import torch
torch.set_num_threads(2)
try:
torch.set_num_interop_threads(2)
except RuntimeError:
# Inter-op pool may already be initialised by an embedding host.
pass
print(f"[Runtime] torch threads={torch.get_num_threads()} interop={torch.get_num_interop_threads()}",
flush=True)
except ImportError:
pass
from Integration_Layer import engine_orchestrator as orchestrator
cfg = orchestrator.build_config(args)
try:
orchestrator.run(args.mode, cfg, args)
except FileNotFoundError as exc:
print(f"\n[RSS] {exc}")
return 2
except KeyboardInterrupt:
print("\n[RSS] interrupted")
return 130
return 0
if __name__ == "__main__":
sys.exit(main())
|