Spaces:
Paused
Paused
| # Dispatches the HF Space container to one of the supported entrypoints | |
| # based on the ENTRYPOINT_MODE environment variable. | |
| # | |
| # Supported values: | |
| # serve (default) — training/serve.py (inference endpoint) | |
| # serve_adversarial Path D — adversarial-paraphrasing inference | |
| # endpoint (training/adversarial_serve.py). | |
| # Same /generate contract as `serve`, but | |
| # internally uses per-token adversarial decoding | |
| # guided by a RoBERTa-Large detector. Requires | |
| # ~16GB GPU (paraphraser 4-bit ~2.4GB + | |
| # detector fp16 ~1GB + KV-cache headroom on | |
| # t4-medium). See R36/R37/R38 in | |
| # .kiro/specs/humanizer-v3-rebuild-and-dpo/. | |
| # serve_v4 v4 — SauerkrautLM-Nemo-12B inference. Same | |
| # /generate contract as `serve` (training/serve.py). | |
| # Target adapter selected via ADAPTER_REPO env var | |
| # (LevArtesa/sft-humanizer-de-v3-{sauerkraut,dpo,rl}-lora); | |
| # for Stage_0 baseline set LOAD_ADAPTER=0 to use the | |
| # bare 12B model. See R29/R32/R38.1 in | |
| # .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| # train_sft_v4 v4 SFT training on SauerkrautLM-Nemo-12B against | |
| # finetune-sft-v4.jsonl from | |
| # LevArtesa/sft-humanizer-dataset-v4 | |
| # (training/train_sft_v4.py). See R12. | |
| # train_sft_v51 v5.1 SFT training on SauerkrautLM-Nemo-12B with | |
| # a manual prefix-masked-loss collator. Reuses | |
| # finetune-sft-v5.jsonl from | |
| # LevArtesa/sft-humanizer-dataset-v5 | |
| # (training/train_sft_v51.py). See | |
| # sft-v5-addendum.md §3 step 1.1 Option 2. | |
| # collect_dpo_pairs_v4 | |
| # v4 DPO preference pair collector — 4 completions | |
| # per block at temperatures {0.5, 0.7, 0.9, 1.1} | |
| # via the SFT v4 adapter, scored via GPTZero, plus | |
| # concatenation with the synthetic pool from R37 | |
| # into v4-dpo-combined.jsonl | |
| # (scripts/collect_dpo_pairs_v4.py). See R18-R20, | |
| # R37.8. | |
| # train_dpo_v4 v4 DPO training on top of SFT v4 adapter — | |
| # trl.DPOTrainer with β=0.1, lr=5e-6, 2 epochs | |
| # against v4-dpo-combined.jsonl | |
| # (training/train_dpo_v4.py). See R21. | |
| # train_rl_v4 v4 RL training pipeline — resumable GRPO on top | |
| # of SFT v4 (or DPO v4) adapter, gated on | |
| # Score_Cache (score-cache-v4.db) to avoid | |
| # re-paying GPTZero for identical completions | |
| # (training/train_rl_v4.py). See R25. | |
| # train v1 training (legacy, training/train.py) | |
| # train_v2 v2 training (training/train_v2.py) | |
| # train_sft SFT training pipeline (training/train_sft.py) | |
| # train_sft_v2 SFT training pipeline against the v2 dataset | |
| # (training/train_sft.py, env-overridden DATASET_PATH | |
| # + SFT_REPO_ID for the v2 LoRA adapter) | |
| # collect_dpo_pairs DPO preference pair collector — for each block | |
| # in the SFT v2 train-set, generates 4 completions | |
| # via the SFT v2 adapter at temperatures | |
| # {0.5, 0.7, 0.9, 1.1}, scores them via GPTZero, | |
| # and emits (prompt, chosen, rejected) triples | |
| # (scripts/collect_dpo_pairs.py) | |
| # train_dpo DPO training pipeline on top of SFT v2 adapter — | |
| # trains trl.DPOTrainer with β=0.1, lr=5e-6, | |
| # 2 epochs against the preference pairs emitted | |
| # by collect_dpo_pairs (training/train_dpo.py) | |
| # train_rl_v3 RL v3 training pipeline with safeguards — | |
| # resumable GRPO on top of the SFT v2 (or DPO) | |
| # adapter, gated on Score_Cache to avoid | |
| # re-paying GPTZero for identical completions | |
| # (training/train_rl_v3.py) | |
| # filter_dataset_v2 Binoculars filter for v2 dataset | |
| # (scripts/filter_with_binoculars.py) | |
| # validate_dataset GPTZero validation pass over an existing dataset | |
| # (scripts/validate_dataset_with_gptzero.py) | |
| # rebuild_dataset Resumable rebuild driver around the GPTZero | |
| # validator (scripts/rebuild_dataset_with_gptzero.py) | |
| # rebuild_dataset_aggressive | |
| # Aggressive-prompt variant of rebuild_dataset — | |
| # swaps the production AI_Version_Generator prompt | |
| # for _AGGRESSIVE_SYSTEM_PROMPT | |
| # (scripts/rebuild_dataset_with_gptzero.py | |
| # --aggressive-prompt) | |
| # evaluate v1 evaluation (legacy, training/evaluate.py) | |
| # evaluate_sft SFT-adapter evaluation harness | |
| # (training/evaluate_sft.py) | |
| # | |
| # Any extra arguments passed to this script are forwarded to the chosen | |
| # Python command (this is required for filter_dataset_v2, which takes | |
| # CLI flags such as --raw-in / --final-out / --threshold). | |
| # | |
| # For testability, passing --dry-run as the first argument prints the | |
| # command that WOULD be executed and exits with status 0 without running | |
| # anything. | |
| set -euo pipefail | |
| # Ensure standalone Python scripts (invoked by absolute path, not -m) can | |
| # still import from the top-level `training/` package. | |
| export PYTHONPATH="/app:${PYTHONPATH:-}" | |
| MODE="${ENTRYPOINT_MODE:-serve}" | |
| # build the command for the selected mode | |
| case "$MODE" in | |
| serve) | |
| CMD=("python" "-m" "training.serve") | |
| ;; | |
| serve_adversarial) | |
| # Path D — adversarial paraphrasing inference endpoint. | |
| # Mirrors the `serve` contract (POST /generate with the same | |
| # payload schema) but internally uses a per-token adversarial | |
| # decoding loop where each candidate token is scored through a | |
| # RoBERTa-Large detector and the lowest-AI-probability token is | |
| # selected. See ``training/adversarial_serve.py`` and R36/R37/R38 | |
| # in ``.kiro/specs/humanizer-v3-rebuild-and-dpo/``. | |
| # | |
| # The default detector ``roberta-large-openai-detector`` (~1.4GB | |
| # fp32 / <1GB fp16) is downloaded from HF on first start and | |
| # cached in the Space's shared HF cache. ``HF_TOKEN`` only matters | |
| # if the (optional) ``ADAPTER_REPO`` is private — the detector | |
| # itself is public. | |
| CMD=("python" "-m" "training.adversarial_serve") | |
| ;; | |
| serve_v4) | |
| # v4 — SauerkrautLM-Nemo-12B inference. Same /generate contract as | |
| # `serve` mode (training/serve.py). The target adapter is selected | |
| # via ADAPTER_REPO env var (one of: | |
| # LevArtesa/sft-humanizer-de-v3-sauerkraut-lora, | |
| # LevArtesa/sft-humanizer-de-v3-dpo-lora, | |
| # LevArtesa/sft-humanizer-de-v3-rl-lora). | |
| # For Stage_0 baseline (R38), set LOAD_ADAPTER=0 to use the bare | |
| # SauerkrautLM-Nemo-12B without any LoRA. See R29 / R38.1 / | |
| # R32 in .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| CMD=("python" "-m" "training.serve") | |
| ;; | |
| train_sft_v4) | |
| # v4 SFT training on SauerkrautLM-Nemo-12B against | |
| # finetune-sft-v4.jsonl. Pre-fetch dataset from | |
| # LevArtesa/sft-humanizer-dataset-v4 (new dataset repo created in | |
| # Stage_1). HF_TOKEN required since dataset repo is private. See | |
| # R12 in .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v4.jsonl ]; then | |
| echo "Downloading finetune-sft-v4.jsonl from LevArtesa/sft-humanizer-dataset-v4 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| finetune-sft-v4.jsonl | |
| fi | |
| # HF Spaces orchestrator probes ``$PORT`` (7860) and kills the | |
| # workload at the 30-minute "not healthy" timeout if no listener | |
| # ever appears. SFT v4 training is a multi-hour batch GPU job | |
| # that never opens any port, so we spawn a tiny background HTTP | |
| # healthcheck server FIRST and trap-kill it on case exit. Mirrors | |
| # the existing ``prep_dataset_v4`` workaround. | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| # --yes skips the interactive Cost_Estimator prompt — required in | |
| # non-interactive Docker (no stdin attached). | |
| CMD=("python" "-m" "training.train_sft_v4" "--yes") | |
| ;; | |
| train_sft_v5) | |
| # v5 SFT training on SauerkrautLM-Nemo-12B against | |
| # finetune-sft-v5.jsonl (~699 records: 500 fresh academic German | |
| # pairs + 199 v3 holdout). Pre-fetch dataset from | |
| # LevArtesa/sft-humanizer-dataset-v5 (new dataset repo created | |
| # during Etap 1). HF_TOKEN required since dataset repo is private. | |
| # See sft-v5-addendum.md §3 Etap 1 step 1.4 in | |
| # .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v5.jsonl ]; then | |
| echo "Downloading finetune-sft-v5.jsonl from LevArtesa/sft-humanizer-dataset-v5 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| finetune-sft-v5.jsonl | |
| fi | |
| # HF Spaces orchestrator probes ``$PORT`` (7860) and kills the | |
| # workload at the 30-minute "not healthy" timeout if no listener | |
| # ever appears. SFT v5 training is a multi-hour batch GPU job | |
| # that never opens any port, so we spawn a tiny background HTTP | |
| # healthcheck server FIRST and trap-kill it on case exit. Mirrors | |
| # the existing ``train_sft_v4`` workaround. | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| # --yes skips the interactive Cost_Estimator prompt — required in | |
| # non-interactive Docker (no stdin attached). | |
| CMD=("python" "-m" "training.train_sft_v5" "--yes") | |
| ;; | |
| train_sft_v51) | |
| # v5.1 SFT training on SauerkrautLM-Nemo-12B against | |
| # finetune-sft-v5.jsonl (REUSED from v5 — same ~699 records: 500 | |
| # fresh academic German pairs + 199 v3 holdout). The trainer adds | |
| # a manual prefix-masked-loss collator (Option 2 in | |
| # sft-v5-addendum.md §3 step 1.1) that finishes the Bug C work | |
| # TRL's ``assistant_only_loss=True`` could not do automatically | |
| # on the Mistral chat template. Pre-fetch dataset from | |
| # LevArtesa/sft-humanizer-dataset-v5 (same repo as v5; nothing | |
| # new on Hub). HF_TOKEN required since dataset repo is private. | |
| # See sft-v5-addendum.md §3 Etap 1 step 1.1 in | |
| # .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v5.jsonl ]; then | |
| echo "Downloading finetune-sft-v5.jsonl from LevArtesa/sft-humanizer-dataset-v5 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| finetune-sft-v5.jsonl | |
| fi | |
| # HF Spaces orchestrator probes ``$PORT`` (7860) and kills the | |
| # workload at the 30-minute "not healthy" timeout if no listener | |
| # ever appears. SFT v5.1 training is a multi-hour batch GPU job | |
| # that never opens any port, so we spawn a tiny background HTTP | |
| # healthcheck server FIRST and trap-kill it on case exit. Mirrors | |
| # the existing ``train_sft_v5`` workaround. | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| # --yes skips the interactive Cost_Estimator prompt — required in | |
| # non-interactive Docker (no stdin attached). | |
| CMD=("python" "-m" "training.train_sft_v51" "--yes") | |
| ;; | |
| collect_dpo_pairs_v4) | |
| # v4 DPO preference pair collector — generates 4 completions per | |
| # block in the SFT v4 train-set at temperatures {0.5, 0.7, 0.9, 1.1} | |
| # via the SFT v4 adapter, scores them via GPTZero, and emits | |
| # (prompt, chosen, rejected) triples to v4-dpo-self.jsonl. Plus | |
| # concatenates with the synthetic pool from R37 into | |
| # v4-dpo-combined.jsonl. Pre-fetches finetune-sft-v4.jsonl, | |
| # v4-dpo-synthetic.jsonl, and any prior checkpoint. HF_TOKEN | |
| # required. See R18-R20, R37.8 in | |
| # .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v4.jsonl ]; then | |
| echo "Downloading finetune-sft-v4.jsonl from LevArtesa/sft-humanizer-dataset-v4 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| finetune-sft-v4.jsonl | |
| fi | |
| if [ ! -f /app/dataset/v4-dpo-synthetic.jsonl ]; then | |
| echo "Downloading v4-dpo-synthetic.jsonl from LevArtesa/sft-humanizer-dataset-v4 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| v4-dpo-synthetic.jsonl | |
| fi | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| v4-dpo-pairs-checkpoint.jsonl 2>/dev/null \ | |
| || echo "(no prior v4 DPO pairs checkpoint)" | |
| CMD=("python" "/app/scripts/collect_dpo_pairs_v4.py") | |
| ;; | |
| train_dpo_v4) | |
| # v4 DPO training on top of SFT v4 adapter — trl.DPOTrainer with | |
| # β=0.1, lr=5e-6, 2 epochs against v4-dpo-combined.jsonl. See R21 | |
| # in .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/v4-dpo-combined.jsonl ]; then | |
| echo "Downloading v4-dpo-combined.jsonl from LevArtesa/sft-humanizer-dataset-v4 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| v4-dpo-combined.jsonl | |
| fi | |
| CMD=("python" "-m" "training.train_dpo_v4") | |
| ;; | |
| train_rl_v4) | |
| # v4 RL training pipeline. GRPO with safeguards: resumable on top | |
| # of SFT v4 (or DPO v4) adapter, gated on Score_Cache to avoid | |
| # re-paying GPTZero for identical completions. Pre-fetches | |
| # finetune-sft-v4.jsonl + any prior score-cache-v4.db. See R25 | |
| # in .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v4.jsonl ]; then | |
| echo "Downloading finetune-sft-v4.jsonl from LevArtesa/sft-humanizer-dataset-v4 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| finetune-sft-v4.jsonl | |
| fi | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| score-cache-v4.db 2>/dev/null \ | |
| || echo "(no prior score cache v4)" | |
| CMD=("python" "-m" "training.train_rl_v4") | |
| ;; | |
| prep_backtrans_only) | |
| # v4 Stage_1 — filter_backtrans only mode (R8). Used to isolate the | |
| # ~3-4h filter step from the costly synth step. Runs Pass 1/2/3, | |
| # uploads the JSONL + meta sidecar to the dataset repo, then | |
| # pauses the Space. The operator triggers fachsprache_anchor and | |
| # synthesize_dpo_pairs separately by switching ENTRYPOINT_MODE. | |
| # | |
| # NOTE: HF Spaces orchestrator probes port 7860 and kills the | |
| # workload at the 30-minute "not healthy" timeout if no listener | |
| # ever appears. The filter is a 3-4h batch job that never opens | |
| # any port, so we spawn a tiny background HTTP healthcheck server | |
| # FIRST and let it run for the lifetime of the container. The | |
| # filter then runs in the foreground; on completion bash exits | |
| # and the container terminates with the healthcheck still bound. | |
| mkdir -p /app/dataset | |
| set -e | |
| echo '=== Spawning healthcheck server on port 7860 (HF Space requirement) ===' | |
| python -c "import http.server, socketserver, threading; h = http.server.SimpleHTTPRequestHandler; srv = socketserver.TCPServer(('0.0.0.0', 7860), h); threading.Thread(target=srv.serve_forever, daemon=True).start(); import time; time.sleep(86400)" & | |
| HEALTHCHECK_PID=$! | |
| sleep 2 | |
| echo " healthcheck pid=$HEALTHCHECK_PID" | |
| echo '=== Stage_1 (backtrans only): filter_backtrans ===' | |
| python /app/scripts/filter_backtrans.py \ | |
| --output /app/dataset/v4-backtrans-filtered.jsonl \ | |
| --meta /app/dataset/v4-backtrans-meta.json \ | |
| --target-size 50000 \ | |
| --seed 42 | |
| echo '=== filter_backtrans done — uploading to dataset repo ===' | |
| # Create the dataset repo on first upload (idempotent). | |
| python -c "import os; from huggingface_hub import HfApi; HfApi(token=os.environ['HF_TOKEN']).create_repo(repo_id='LevArtesa/sft-humanizer-dataset-v4', repo_type='dataset', exist_ok=True, private=False)" | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/v4-backtrans-filtered.jsonl \ | |
| v4-backtrans-filtered.jsonl | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/v4-backtrans-meta.json \ | |
| v4-backtrans-meta.json | |
| echo '=== upload complete. Pausing Space. ===' | |
| kill "$HEALTHCHECK_PID" 2>/dev/null || true | |
| python -c "import os; from huggingface_hub import HfApi; HfApi(token=os.environ['HF_TOKEN']).pause_space(os.environ.get('SPACE_ID', 'LevArtesa/humanizer-v4-sauerkraut'))" || true | |
| CMD=("echo" "Stage_1 backtrans-only complete — Space paused") | |
| ;; | |
| prep_dataset_v4) | |
| # v4 Stage_1 dataset preparation pipeline. Runs all four steps in | |
| # sequence on the Space: | |
| # 1. filter_backtrans.py -> dataset/v4-backtrans-filtered.jsonl (50k) | |
| # 2. build_fachsprache_anchor.py -> dataset/v4-fachsprache-anchor.jsonl (~12k) | |
| # 3. synthesize_dpo_pairs.py -> dataset/v4-dpo-synthetic.jsonl (~7500, OpenRouter) | |
| # 4. build_sft_v4_dataset.py -> dataset/finetune-sft-v4.jsonl (atomic concat) | |
| # Plus uploads results to LevArtesa/sft-humanizer-dataset-v4 dataset | |
| # repo. CPU-bound — t4-medium GPU is unused but Space hardware was | |
| # already requested by the operator. OPENROUTER_API_KEY must be set | |
| # in the Space secrets. HF_TOKEN required for cloud_sync. See R8, | |
| # R9, R10, R11, R37 in | |
| # .kiro/specs/humanizer-v4-sauerkraut-12b/. | |
| # | |
| # NOTE (2026-XX-XX): HF Spaces orchestrator probes ``$PORT`` (7860) | |
| # and kills the workload at the 30-minute "not healthy" timeout if | |
| # nothing is bound. The Stage_1 pipeline is a multi-hour batch job | |
| # that never opens any port, so we spawn a tiny background HTTP | |
| # healthcheck server FIRST and trap-kill it on case exit. Mirrors | |
| # the existing ``prep_backtrans_only`` workaround. | |
| mkdir -p /app/dataset | |
| set -e | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| echo '=== Stage_1 step 1/4: filter_backtrans ===' | |
| python /app/scripts/filter_backtrans.py \ | |
| --output /app/dataset/v4-backtrans-filtered.jsonl \ | |
| --target-size 50000 \ | |
| --seed 42 | |
| echo '=== Stage_1 step 2/4: build_fachsprache_anchor ===' | |
| python /app/scripts/build_fachsprache_anchor.py \ | |
| --output /app/dataset/v4-fachsprache-anchor.jsonl \ | |
| --target-size 12000 | |
| echo '=== Stage_1 step 3/4: synthesize_dpo_pairs ===' | |
| python /app/scripts/synthesize_dpo_pairs.py \ | |
| --output /app/dataset/v4-dpo-synthetic.jsonl \ | |
| --target 7500 \ | |
| --cost-cap-usd 350 | |
| echo '=== Stage_1 step 4/4: build_sft_v4_dataset ===' | |
| python /app/scripts/build_sft_v4_dataset.py \ | |
| --backtrans /app/dataset/v4-backtrans-filtered.jsonl \ | |
| --fachsprache /app/dataset/v4-fachsprache-anchor.jsonl \ | |
| --v3-holdout /app/dataset/finetune-sft-v2.jsonl \ | |
| --output /app/dataset/finetune-sft-v4.jsonl \ | |
| --meta /app/dataset/finetune-sft-v4.meta.json \ | |
| --sanity-sample /app/dataset/finetune-sft-v4.sanity-sample.jsonl | |
| echo '=== Stage_1 done. Uploading to dataset repo ===' | |
| # Upload all v4 dataset artifacts via huggingface-cli. The dataset | |
| # repo is created on first upload via api.create_repo. | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/finetune-sft-v4.jsonl \ | |
| finetune-sft-v4.jsonl | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/finetune-sft-v4.meta.json \ | |
| finetune-sft-v4.meta.json | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/v4-backtrans-filtered.jsonl \ | |
| v4-backtrans-filtered.jsonl | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/v4-fachsprache-anchor.jsonl \ | |
| v4-fachsprache-anchor.jsonl | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/v4-dpo-synthetic.jsonl \ | |
| v4-dpo-synthetic.jsonl | |
| huggingface-cli upload \ | |
| --repo-type dataset \ | |
| LevArtesa/sft-humanizer-dataset-v4 \ | |
| /app/dataset/finetune-sft-v4.sanity-sample.jsonl \ | |
| finetune-sft-v4.sanity-sample.jsonl | |
| echo '=== Stage_1 uploads complete. Pausing Space. ===' | |
| # Auto-pause via shutdown endpoint (best-effort, exit 0 on failure). | |
| python -c "import os; from huggingface_hub import HfApi; HfApi(token=os.environ['HF_TOKEN']).pause_space(os.environ.get('SPACE_ID', 'LevArtesa/humanizer-v4-sauerkraut'))" || true | |
| CMD=("echo" "Stage_1 complete — Space paused") | |
| ;; | |
| train_german_guide_v5) | |
| # v5 Stage_V_0 — fine-tune ``xlm-roberta-base`` as a German guide | |
| # detector for Path D adversarial decoding (R19). Binary | |
| # classifier trained on the 530 etap1 academic + 199 v3 holdout | |
| # records from ``finetune-sft-v5.jsonl`` (``human`` → label=0, | |
| # ``ai`` → label=1, R19.1). On completion the classifier is | |
| # published to ``LevArtesa/german-ai-detector-v5-guide`` (R19.3). | |
| # Pre-fetches the v5 dataset from | |
| # ``LevArtesa/sft-humanizer-dataset-v5``; HF_TOKEN required since | |
| # the dataset repo is private. ``.env`` (if present at /app/.env) | |
| # is sourced first per design D4 so HF_TOKEN / GPTZERO_API_KEY are | |
| # available on dev machines without manual export. Hardware tier | |
| # ``t4-medium`` (R37.4). See | |
| # .kiro/specs/humanizer-v5-detector-in-the-loop/ R19. | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v5.jsonl ]; then | |
| echo "Downloading finetune-sft-v5.jsonl from LevArtesa/sft-humanizer-dataset-v5 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| finetune-sft-v5.jsonl | |
| fi | |
| # HF Spaces orchestrator probes ``$PORT`` (7860) and kills the | |
| # workload at the 30-minute "not healthy" timeout if no listener | |
| # ever appears. Detector training is a multi-minute batch GPU | |
| # job that never opens any port, so we spawn a tiny background | |
| # HTTP healthcheck server FIRST and trap-kill it on case exit. | |
| # Mirrors the existing ``train_sft_v5`` workaround. | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| CMD=("python" "-m" "training.train_german_guide_v5") | |
| ;; | |
| serve_path_d_v5) | |
| # v5 Stage_V_0 — Path D adversarial-paraphrasing inference with | |
| # the German guide detector replacing the v3 RoBERTa-OpenAI | |
| # default. Reuses ``training/adversarial_serve.py`` unchanged | |
| # except for the additive ``GUIDE_DETECTOR_REPO`` env-var gate | |
| # (R11.5 / R20.1). Loads SauerkrautLM-Nemo-12B with the v5.1 | |
| # LoRA adapter ``LevArtesa/sft-humanizer-de-v3-sauerkraut-v51-lora`` | |
| # — no new policy training in Stage_V_0 (R20.2). Hardware tier | |
| # ``t4-medium`` (R20.4 / R37.4). See | |
| # .kiro/specs/humanizer-v5-detector-in-the-loop/ R20. | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| export GUIDE_DETECTOR_REPO="${GUIDE_DETECTOR_REPO:-LevArtesa/german-ai-detector-v5-guide}" | |
| export ADAPTER_REPO="${ADAPTER_REPO:-LevArtesa/sft-humanizer-de-v3-sauerkraut-v51-lora}" | |
| # MODEL_REPO MUST be the 12B base the v5.1 LoRA was trained on | |
| # (design §3.1 / R20.2). adversarial_serve.py defaults MODEL_REPO to | |
| # Qwen/Qwen3-4B (the v3 Path D paraphraser); leaving it unset here would | |
| # make PeftModel.from_pretrained fail to apply the 12B LoRA onto the 4B | |
| # base, get swallowed by the adapter-load except, and SILENTLY serve bare | |
| # Qwen3-4B — burning the whole serve GPU run + 27 GPTZero blocks on a | |
| # meaningless result. Pin it to SauerkrautLM-Nemo-12B (operator override | |
| # still wins via ${MODEL_REPO:-...}). | |
| export MODEL_REPO="${MODEL_REPO:-VAGOsolutions/SauerkrautLM-Nemo-12b-Instruct}" | |
| CMD=("python" "-m" "training.adversarial_serve") | |
| ;; | |
| train_surrogate_v5) | |
| # v5 Stage_V_1 — three-phase orchestration of the GPTZero | |
| # surrogate detector (R22-R24). The phase is selected by the | |
| # ``V5_SURROGATE_PHASE`` env var: | |
| # dataset — assemble + GPTZero-score 5000-10000 German | |
| # paragraphs into ``dataset/v5-surrogate-train.jsonl`` | |
| # (R22, atomic per-record write per R4) | |
| # train — fine-tune ``xlm-roberta-base`` regressor on the | |
| # assembled dataset and publish to | |
| # ``LevArtesa/gptzero-surrogate-de-v5`` (R23) | |
| # validate — compute R² on the held-out 20% and write | |
| # ``decision-gate-V-1.json`` (R24) | |
| # Best-effort pre-fetches the partial | |
| # ``v5-surrogate-train.jsonl`` from the HF dataset repo so a | |
| # resumed run skips already-scored paragraphs via score_cache | |
| # (R15.3, R22.3). HF_TOKEN / GPTZERO_API_KEY / OPENROUTER_API_KEY | |
| # required — sourced from ``/app/.env`` if present per design D4. | |
| # Hardware tier ``t4-medium`` (R37.4 — 270M xlm-roberta fits). | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| mkdir -p /app/dataset | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| v5-surrogate-train.jsonl 2>/dev/null \ | |
| || echo "(no prior v5 surrogate dataset — fresh assembly)" | |
| # The held-out 20% validation split is REQUIRED by the train/validate | |
| # phases for the R² gate (R23.2 / R24); pre-fetch it best-effort alongside | |
| # the train split. Absent in the `dataset` phase (it is produced there), so | |
| # a miss is non-fatal. | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| v5-surrogate-validation.jsonl 2>/dev/null \ | |
| || echo "(no v5 surrogate validation split — fresh assembly will produce it)" | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| CMD=("python" "-m" "training.train_surrogate_v5") | |
| ;; | |
| train_grpo_v5) | |
| # v5 Stage_V_2 — GRPO on SauerkrautLM-Nemo-12B + v5.1 LoRA with | |
| # the GPTZero surrogate as cheap reward and a KL anchor to the | |
| # frozen v5.1 reference policy (R26-R29). The trainable adapter | |
| # is published to ``LevArtesa/grpo-humanizer-de-v5-lora`` (NEW | |
| # repo, never overwrites v5.1, R26.5). Pre-fetches the v5.1 | |
| # source dataset and best-effort pre-fetches any prior GRPO | |
| # checkpoint dir + cumulative reward log so a Space restart | |
| # resumes from the last persisted GRPO step (R16.3). Hardware | |
| # tier ``a10g-large`` (R37.4) — 12B base + LoRA r=64 trainable + | |
| # LoRA reference + surrogate (270M) + sentence-transformer | |
| # together require ~22GB peak. See | |
| # .kiro/specs/humanizer-v5-detector-in-the-loop/ R26-R29. | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v5.jsonl ]; then | |
| echo "Downloading finetune-sft-v5.jsonl from LevArtesa/sft-humanizer-dataset-v5 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| finetune-sft-v5.jsonl | |
| fi | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| v5-grpo-rewards.jsonl 2>/dev/null \ | |
| || echo "(no prior GRPO reward log — fresh run)" | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| v5-grpo-checkpoint.tar 2>/dev/null \ | |
| || echo "(no prior GRPO checkpoint — fresh run)" | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| CMD=("python" "-m" "training.train_grpo_v5") | |
| ;; | |
| serve_grpo_v5) | |
| # v5 Stage_V_2 / Stage_V_4 — production serve mode for the | |
| # GRPO-trained adapter ``LevArtesa/grpo-humanizer-de-v5-lora``. | |
| # Mirrors the ``serve`` contract (training/serve.py) with | |
| # ``ADAPTER_REPO`` pinned to the v5 GRPO output (R34.2). Hardware | |
| # tier ``t4-medium``. | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| export ADAPTER_REPO="${ADAPTER_REPO:-LevArtesa/grpo-humanizer-de-v5-lora}" | |
| CMD=("python" "-m" "training.serve") | |
| ;; | |
| train_authormist_v5) | |
| # v5 Stage_V_3 — fallback fine-tune of the published AuthorMist | |
| # Originality model (1-3B base) on the 530 etap1 academic German | |
| # pairs (R31). Triggered ONLY when Stage_V_2 yields | |
| # ``success_rate ∈ [0.4, 0.6)`` per R30.5. The base repo is | |
| # selected via the ``AUTHORMIST_BASE_REPO`` env var, defaulting | |
| # to ``Aman90101/test`` (R31.1). The trained adapter is published | |
| # to ``LevArtesa/authormist-humanizer-de-v5-lora`` (NEW repo). | |
| # Hardware tier ``t4-medium`` — 1-3B base with LoRA r=32 fits in | |
| # 16GB (R31.4 / R37.4). See | |
| # .kiro/specs/humanizer-v5-detector-in-the-loop/ R31. | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| export AUTHORMIST_BASE_REPO="${AUTHORMIST_BASE_REPO:-Aman90101/test}" | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v5.jsonl ]; then | |
| echo "Downloading finetune-sft-v5.jsonl from LevArtesa/sft-humanizer-dataset-v5 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v5 \ | |
| finetune-sft-v5.jsonl | |
| fi | |
| HF_PORT="${PORT:-7860}" | |
| python3 -m http.server "$HF_PORT" --bind 0.0.0.0 >/tmp/healthcheck.log 2>&1 & | |
| HEALTHCHECK_PID=$! | |
| trap 'kill $HEALTHCHECK_PID 2>/dev/null || true' EXIT | |
| echo "Health-check HTTP server started on port $HF_PORT (pid=$HEALTHCHECK_PID)" | |
| CMD=("python" "-m" "training.train_authormist_v5" "--yes") | |
| ;; | |
| serve_authormist_v5) | |
| # v5 Stage_V_3 / Stage_V_4 — production serve mode for the | |
| # AuthorMist fallback adapter | |
| # ``LevArtesa/authormist-humanizer-de-v5-lora``. Mirrors the | |
| # ``serve`` contract (training/serve.py) with both the adapter | |
| # repo and the AuthorMist base model selected via env vars (so | |
| # an operator can swap to a different AuthorMist checkpoint | |
| # without code change, R31.1 / R34.2). ``BASE_MODEL_REPO`` is | |
| # propagated from ``AUTHORMIST_BASE_REPO`` for parity with the | |
| # ``train_authormist_v5`` case branch. | |
| set -a; [ -f /app/.env ] && source /app/.env; set +a | |
| export AUTHORMIST_BASE_REPO="${AUTHORMIST_BASE_REPO:-Aman90101/test}" | |
| export ADAPTER_REPO="${ADAPTER_REPO:-LevArtesa/authormist-humanizer-de-v5-lora}" | |
| export BASE_MODEL_REPO="$AUTHORMIST_BASE_REPO" | |
| # serve.py reads the base via MODEL_REPO (os.environ.get("MODEL_REPO", ...)), | |
| # NOT BASE_MODEL_REPO. Without this the serve would silently load the | |
| # Qwen3-4B default instead of the AuthorMist base (Qwen2.5-3B), and our | |
| # 3B LoRA would fail to apply onto the 4B base — burning the serve GPU + | |
| # 27 GPTZero blocks on a meaningless result. Pin MODEL_REPO to the | |
| # AuthorMist base (operator override still wins via the env above). | |
| export MODEL_REPO="$AUTHORMIST_BASE_REPO" | |
| CMD=("python" "-m" "training.serve") | |
| ;; | |
| train) | |
| CMD=("python" "-m" "training.train") | |
| ;; | |
| train_v2) | |
| CMD=("python" "-m" "training.train_v2") | |
| ;; | |
| train_sft) | |
| # Pre-fetch the SFT dataset from the private HF dataset repo before | |
| # invoking the trainer. Skipped if the file is already on disk so a | |
| # Space restart resumes from local cache without a redundant | |
| # download. ``HF_TOKEN`` is required since the dataset repo is | |
| # private — the Space already has it as a secret. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft.jsonl ]; then | |
| echo "Downloading finetune-sft.jsonl from LevArtesa/sft-humanizer-dataset-v1 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v1 \ | |
| finetune-sft.jsonl | |
| fi | |
| CMD=("python" "-m" "training.train_sft") | |
| ;; | |
| train_sft_v2) | |
| # SFT training against the v2 (aggressively-rebuilt) dataset. | |
| # Re-uses ``training/train_sft.py`` unchanged (Requirement 6.16 | |
| # reuse mandate) — only the dataset path and destination LoRA | |
| # repo differ from ``train_sft``. Both are exported as env vars | |
| # so the trainer module picks them up without any code change. | |
| export DATASET_PATH=/app/dataset/finetune-sft-v2.jsonl | |
| export SFT_REPO_ID=LevArtesa/sft-humanizer-de-v2-lora | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v2.jsonl ]; then | |
| echo "Downloading finetune-sft-v2.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| finetune-sft-v2.jsonl | |
| fi | |
| if [ ! -f /app/dataset/holdout-v2.jsonl ]; then | |
| echo "Downloading holdout-v2.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| holdout-v2.jsonl | |
| fi | |
| CMD=("python" "-m" "training.train_sft") | |
| ;; | |
| collect_dpo_pairs) | |
| # DPO preference pair collector — generates 4 completions per | |
| # block at temperatures {0.5, 0.7, 0.9, 1.1} via the SFT v2 | |
| # adapter, scores them via GPTZero, and emits | |
| # (prompt, chosen, rejected) triples to dpo-pairs.jsonl. | |
| # Pre-fetches the SFT v2 train-set so the collector can read its | |
| # prompts without a separate download, plus any prior DPO pair | |
| # checkpoint so a Space restart resumes from where it left off. | |
| # ``HF_TOKEN`` is required since both dataset repos are private. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v2.jsonl ]; then | |
| echo "Downloading finetune-sft-v2.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| finetune-sft-v2.jsonl | |
| fi | |
| # Best-effort pre-fetch of any prior DPO pair checkpoint so a | |
| # restarted container resumes from the last persisted block. If | |
| # no checkpoint exists yet on the dataset repo, the collector | |
| # simply starts fresh — the missing-file error is swallowed. | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| dpo-pairs-checkpoint.jsonl 2>/dev/null \ | |
| || echo "(no prior DPO checkpoint)" | |
| CMD=("python" "/app/scripts/collect_dpo_pairs.py") | |
| ;; | |
| train_dpo) | |
| # DPO training on top of the SFT v2 adapter — consumes the | |
| # preference pairs emitted by ``collect_dpo_pairs`` and trains | |
| # ``trl.DPOTrainer`` with β=0.1, learning_rate=5e-6, | |
| # num_train_epochs=2 (per Requirement 18). Pre-fetches the | |
| # ``dpo-pairs.jsonl`` file from the v2 dataset repo unless it is | |
| # already on disk, so a Space restart resumes from local cache | |
| # without a redundant download. ``HF_TOKEN`` is required since | |
| # the dataset repo is private — the Space already has it as a | |
| # secret. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/dpo-pairs.jsonl ]; then | |
| echo "Downloading dpo-pairs.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| dpo-pairs.jsonl | |
| fi | |
| CMD=("python" "-m" "training.train_dpo") | |
| ;; | |
| train_rl_v3) | |
| # RL v3 training pipeline with safeguards. Resumable GRPO on top | |
| # of the SFT v2 (or DPO) adapter that gates every reward lookup | |
| # on a persistent Score_Cache to avoid re-paying GPTZero for | |
| # identical completions across restarts. Pre-fetches the v2 SFT | |
| # dataset and hold-out (used as prompt source for rollouts) plus | |
| # any prior score-cache so the cache survives Space restarts. | |
| # ``HF_TOKEN`` is required since the dataset repo is private. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/finetune-sft-v2.jsonl ]; then | |
| echo "Downloading finetune-sft-v2.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| finetune-sft-v2.jsonl | |
| fi | |
| if [ ! -f /app/dataset/holdout-v2.jsonl ]; then | |
| echo "Downloading holdout-v2.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| holdout-v2.jsonl | |
| fi | |
| # Best-effort pre-fetch of any prior score cache so the RL run | |
| # resumes with all previously-paid GPTZero scores intact (saves | |
| # real $$ across Space restarts). If no cache exists yet on the | |
| # dataset repo, the trainer simply starts with an empty cache — | |
| # the missing-file error is swallowed. | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| score-cache.db 2>/dev/null \ | |
| || echo "(no prior score cache)" | |
| CMD=("python" "-m" "training.train_rl_v3") | |
| ;; | |
| filter_dataset_v2) | |
| # Pre-fetch the raw JSONL from the private HF dataset repo. Using | |
| # huggingface-cli avoids an extra Python wrapper and respects | |
| # HF_TOKEN / HF_HOME automatically. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/v2-raw.jsonl ]; then | |
| echo "Downloading v2-raw.jsonl from LevArtesa/grpo-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/grpo-humanizer-dataset-v2 \ | |
| v2-raw.jsonl | |
| fi | |
| # Reduce CUDA fragmentation — attention allocations for the | |
| # Falcon-7B pair at len=2048 are large and transient. | |
| export PYTORCH_CUDA_ALLOC_CONF="expandable_segments:True" | |
| CMD=( | |
| "python" "/app/scripts/filter_with_binoculars.py" | |
| "--raw-in" "/app/dataset/v2-raw.jsonl" | |
| "--final-out" "/app/dataset/finetune-v2.jsonl" | |
| "--threshold" "0.7" | |
| "--batch-size" "8" | |
| ) | |
| ;; | |
| evaluate) | |
| CMD=("python" "-m" "training.evaluate") | |
| ;; | |
| evaluate_sft) | |
| # Pre-fetch the hold-out JSONL from the SFT dataset repo so the | |
| # evaluator can score it without re-running SFTPreparer (same | |
| # seed=42 → identical 40 records). The progress JSONL written | |
| # alongside ``--report-path`` makes the run resumable across | |
| # container restarts: rerunning this command picks up after the | |
| # last persisted record. Pre-fetch any prior progress JSONL so | |
| # the resume actually works after a Space restart. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/holdout.jsonl ]; then | |
| echo "Downloading holdout.jsonl from LevArtesa/sft-humanizer-dataset-v1 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v1 \ | |
| holdout.jsonl | |
| fi | |
| mkdir -p /home/user/output | |
| # Try to fetch a previously persisted progress file so a restarted | |
| # container resumes from where it left off (the previous run on | |
| # 2026-05-15 died at record ~32/40 due to KV-cache blow-up at | |
| # ``max_new_tokens=2048``). The download is best-effort: if no | |
| # progress exists yet, the file is simply absent and the eval | |
| # starts from scratch. | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /home/user/output \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v1 \ | |
| sft-eval-progress.jsonl 2>/dev/null \ | |
| && mv /home/user/output/sft-eval-progress.jsonl \ | |
| /home/user/output/sft-eval-report.json.progress.jsonl \ | |
| || echo "(no prior progress JSONL — fresh eval start)" | |
| CMD=( | |
| "python" "-m" "training.evaluate_sft" | |
| "--adapter-repo" "LevArtesa/sft-humanizer-de-v1-lora" | |
| "--holdout-path" "/app/dataset/holdout.jsonl" | |
| "--report-path" "/home/user/output/sft-eval-report.json" | |
| "--max-new-tokens" "1024" | |
| ) | |
| ;; | |
| evaluate_sft_v2) | |
| # Hold-out evaluation of the SFT v2 adapter against the v2 hold-out | |
| # JSONL (deterministic seed=42, 40 records). | |
| # Pre-fetches the v2 hold-out and any prior progress JSONL so the | |
| # eval is resumable across restarts. Targets | |
| # ``LevArtesa/sft-humanizer-de-v2-lora`` (R14.1, R14.2). | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/holdout-v2.jsonl ]; then | |
| echo "Downloading holdout-v2.jsonl from LevArtesa/sft-humanizer-dataset-v2 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| holdout-v2.jsonl | |
| fi | |
| mkdir -p /home/user/output | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /home/user/output \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| sft-eval-v2-progress.jsonl 2>/dev/null \ | |
| && mv /home/user/output/sft-eval-v2-progress.jsonl \ | |
| /home/user/output/sft-eval-v2-report.json.progress.jsonl \ | |
| || echo "(no prior v2 progress JSONL — fresh eval start)" | |
| CMD=( | |
| "python" "-m" "training.evaluate_sft" | |
| "--adapter-repo" "LevArtesa/sft-humanizer-de-v2-lora" | |
| "--holdout-path" "/app/dataset/holdout-v2.jsonl" | |
| "--report-path" "/home/user/output/sft-eval-v2-report.json" | |
| "--max-new-tokens" "1024" | |
| ) | |
| ;; | |
| validate_dataset) | |
| CMD=("python" "/app/scripts/validate_dataset_with_gptzero.py") | |
| ;; | |
| rebuild_dataset) | |
| CMD=("python" "/app/scripts/rebuild_dataset_with_gptzero.py") | |
| ;; | |
| rebuild_dataset_aggressive) | |
| # Aggressive-prompt rebuild driver around the GPTZero validator. | |
| # Pre-fetches the cleaned v2 input JSONL and any prior rebuild | |
| # checkpoint so a Space restart resumes from where it left off. | |
| # The --aggressive-prompt flag is appended to CMD so the rebuilder | |
| # swaps the production system prompt for _AGGRESSIVE_SYSTEM_PROMPT | |
| # (see scripts/pilot_rebuild_test.py). HF_TOKEN is required since | |
| # both dataset repos are private. | |
| mkdir -p /app/dataset | |
| if [ ! -f /app/dataset/v2-clean.jsonl ]; then | |
| echo "Downloading v2-clean.jsonl from LevArtesa/sft-humanizer-dataset-v1 ..." | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v1 \ | |
| v2-clean.jsonl | |
| fi | |
| # Best-effort pre-fetch of any prior rebuild checkpoint so a | |
| # restarted container resumes from where it left off. If no | |
| # checkpoint exists yet on the dataset repo, the rebuilder simply | |
| # starts fresh — the missing-file error is swallowed. | |
| huggingface-cli download \ | |
| --repo-type dataset \ | |
| --local-dir /app/dataset \ | |
| --local-dir-use-symlinks False \ | |
| LevArtesa/sft-humanizer-dataset-v2 \ | |
| rebuild-checkpoint.json 2>/dev/null \ | |
| || echo "(no prior rebuild checkpoint)" | |
| CMD=("python" "/app/scripts/rebuild_dataset_with_gptzero.py" "--aggressive-prompt") | |
| ;; | |
| *) | |
| echo "Unknown ENTRYPOINT_MODE='$MODE'. Falling back to 'serve'." >&2 | |
| CMD=("python" "-m" "training.serve") | |
| ;; | |
| esac | |
| # --dry-run handling: if first arg is --dry-run, just print and exit 0 | |
| if [ "${1:-}" = "--dry-run" ]; then | |
| shift | |
| printf "%s " "${CMD[@]}" | |
| printf "%s " "$@" | |
| echo | |
| exit 0 | |
| fi | |
| exec "${CMD[@]}" "$@" | |