2026-03-25 23:05:45 +00:00
# AutoLoRA Training Pipeline
# Replaces: autolora repo (1,500 lines) with config + make targets
#
# Prerequisites:
# pip install axolotl mlx-lm lm-evaluation-harness pyyaml
#
# Targets:
2026-04-17 06:39:05 +00:00
# make train-cloud -- QLoRA on cloud GPU via Axolotl
# make train-local -- LoRA on Apple Silicon via MLX
# make eval -- Standard benchmarks via lm-eval-harness
# make vibes -- Hand-picked prompts through Ollama, human review
# make ingest -- Pull heartbeat trajectories into training data
# make curated -- Regenerate curated exemplar dataset
2026-03-25 23:05:45 +00:00
2026-04-17 06:39:05 +00:00
PYTHON ?= python3
2026-03-25 23:05:45 +00:00
MODEL ?= timmy:v0.1-q4
BASELINE ?= hermes3:latest
OLLAMA_URL ?= http://localhost:11434
OUTPUT ?= output
2026-04-17 06:39:05 +00:00
# -- Training --
2026-03-25 23:05:45 +00:00
train-cloud : ## QLoRA fine-tune on cloud GPU (Axolotl)
axolotl train axolotl.yaml
train-local : ## LoRA fine-tune on Apple Silicon (MLX)
2026-04-17 02:37:47 -04:00
$( PYTHON) -m mlx_lm.lora --config mlx-lora.yaml
2026-03-25 23:05:45 +00:00
2026-04-17 06:39:05 +00:00
# -- Evaluation --
2026-03-25 23:05:45 +00:00
eval : ## Run standard benchmarks against Ollama model
lm_eval --model local-completions \
--model_args " model= $( MODEL) ,base_url= $( OLLAMA_URL) /v1,tokenized_requests=False " \
--tasks hellaswag,truthfulqa_mc2,arc_challenge,winogrande \
--output_path evals_archive/$( MODEL) /
@echo " Results in evals_archive/ $( MODEL) / "
eval-baseline : ## Run same benchmarks against baseline for comparison
lm_eval --model local-completions \
--model_args " model= $( BASELINE) ,base_url= $( OLLAMA_URL) /v1,tokenized_requests=False " \
--tasks hellaswag,truthfulqa_mc2,arc_challenge,winogrande \
--output_path evals_archive/$( BASELINE) /
2026-04-17 06:39:05 +00:00
vibes : ## Run vibes check -- hand-picked prompts, human review
2026-03-25 23:05:45 +00:00
@echo " === Vibes Check: $( MODEL) === "
@echo " Date: $$ (date '+%Y-%m-%d %H:%M') " > $( OUTPUT) /vibes-$( MODEL) .md
@echo " Model: $( MODEL) " >> $( OUTPUT) /vibes-$( MODEL) .md
@echo "" >> $( OUTPUT) /vibes-$( MODEL) .md
2026-04-17 06:39:05 +00:00
@$( PYTHON) -c " import yaml, subprocess, sys; prompts = yaml.safe_load(open('data/prompts_vibes.yaml'))['prompts']; f = open(' $( OUTPUT) /vibes- $( MODEL) .md', 'a'); [(sys.stdout.write(f' [{p[\"id\"]}] {p[\"category\"]}...'), sys.stdout.flush(), f.write(f'## [{p[\"id\"]}] {p[\"category\"]}\n'), f.write(f'PROMPT: {p[\"prompt\"]}\n'), f.write(f'EXPECTED: {p[\"expected\"]}\n\n'), f.write('RESPONSE:\n'), f.write(subprocess.run(['ollama', 'run', ' $( MODEL) ', p['prompt']], capture_output=True, text=True, timeout=120).stdout), f.write('\n\nSCORE: ___/5\n\n---\n\n'), print(' done')) for p in prompts]; f.close() "
@echo " Output: $( OUTPUT) /vibes- $( MODEL) .md -- fill in scores manually. "
adversary-value-violations : ## Run 200-prompt value-violations adversary suite
2026-04-16 05:12:07 +00:00
@mkdir -p $( OUTPUT) /adversary-value-violations
2026-04-17 06:39:05 +00:00
$( PYTHON) run_adversary_eval.py \
--suite data/prompts_adversary_value_violations.yaml \
--model $( MODEL) \
--output-dir $( OUTPUT) /adversary-value-violations
2026-04-16 05:12:07 +00:00
@echo " Output: $( OUTPUT) /adversary-value-violations "
2026-04-17 06:39:05 +00:00
# -- Data Pipeline --
2026-03-25 23:05:45 +00:00
ingest : ## Pull heartbeat trajectories into training data
2026-04-17 02:37:47 -04:00
$( PYTHON) ingest_trajectories.py \
2026-03-25 23:05:45 +00:00
--trajectories ~/.nexus/trajectories/ \
--curated data/curated_dataset.jsonl \
--output data/merged_training_data.jsonl
@echo "Merged dataset ready. Convert for MLX with: make convert"
curated : ## Regenerate curated exemplar dataset
2026-04-17 02:37:47 -04:00
$( PYTHON) build_curated.py
2026-03-25 23:05:45 +00:00
@echo "Curated dataset regenerated."
convert : ## Convert merged dataset to MLX format (train/valid split)
@mkdir -p data/mlx_curated
2026-04-17 06:39:05 +00:00
$( PYTHON) -c "import json; lines = open('data/merged_training_data.jsonl').readlines(); sessions = [json.loads(l) for l in lines]; ROLE_MAP = {'system':'system','human':'user','gpt':'assistant','tool':'user'}; converted = [{'messages': [{'role': ROLE_MAP.get(t.get('from',''),'user'), 'content': t.get('value','')} for t in s.get('conversations',[])]} for s in sessions]; split = max(1, int(len(converted)*0.9)); open('data/mlx_curated/train.jsonl','w').writelines(json.dumps(c)+'\n' for c in converted[:split]); open('data/mlx_curated/valid.jsonl','w').writelines(json.dumps(c)+'\n' for c in converted[split:]); print(f'train: {split}, valid: {len(converted)-split}')"
# -- Helpers --
2026-03-25 23:05:45 +00:00
2026-04-16 05:12:07 +00:00
.PHONY : train -cloud train -local eval eval -baseline vibes adversary -value -violations ingest curated convert help
2026-03-25 23:05:45 +00:00
help : ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $( MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'