path-d-humanizer / entrypoint.sh
LevArtesa's picture
Stage_V_3 serve: upload entrypoint.sh
416e204 verified
Raw
History Blame Contribute Delete
47.1 kB
#!/usr/bin/env bash
# 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[@]}" "$@"