Fine-tune an open-source LLM on your own writing so the model produces text in your voice — your vocabulary, sentence rhythm, formality, and habits — not the generic LLM house style.
The project implements the full pipeline from raw Google Drive / Gmail exports to a trained QLoRA adapter and an inference UI, with evaluation harnesses for automatic metrics, AI-detector checks, and blind human A/B testing.
| Stage | Module | CLI entry point |
|---|---|---|
| Google Drive ingest | src/ingestion/drive_downloader.py | python -m src.ingestion.drive_downloader |
| File conversion | src/ingestion/file_converter.py | python -m src.ingestion.file_converter |
| Gmail mbox → text | scripts/convert_mbox_to_text.py | python scripts/convert_mbox_to_text.py |
| Text cleaning + PII | src/preprocessing/text_cleaner.py | python -m src.preprocessing.text_cleaner |
| Dataset construction | src/preprocessing/dataset_creator.py | python -m src.preprocessing.dataset_creator |
| QLoRA fine-tuning | src/training/finetune_qlora.py | python -m src.training.finetune_qlora |
| Hyperparameter sweep | src/training/sweep.py | python -m src.training.sweep |
| Ablation studies | src/training/ablations.py | python -m src.training.ablations |
| Generation (CLI) | src/inference/generator.py | python -m src.inference.generator |
| Generation (API) | src/inference/server.py | python -m src.inference.server |
| Generation (UI) | ui/app.py | streamlit run ui/app.py |
| Style metrics | src/evaluation/metrics.py | python -m src.evaluation.metrics |
| AI-detector testing | src/evaluation/detector_test.py | python -m src.evaluation.detector_test |
| Blind A/B harness | src/evaluation/ab_test.py | python -m src.evaluation.ab_test |
| Colab training notebook | notebooks/train_colab.ipynb | Open in Colab, runtime → T4 GPU |
- Install.
./setup.sh(creates venv, installs requirements, downloads spaCyen_core_web_sm). - Configure.
cp configs/config.example.yaml configs/config.yamland fill in Google Drive folder IDs. If you'd rather skip Drive, drop your.txt/.docx/.pdffiles straight intodata/raw/. - Ingest + clean + build dataset.
./run_pipeline.sh(interactive; runs each stage with your confirmation) producesdata/datasets/train.jsonlandtest.jsonlin Alpaca format. - Train. Either:
- Free GPU (Colab, recommended): open notebooks/train_colab.ipynb, upload your JSONL files, and run all cells. The notebook trains Llama-3.2-3B-Instruct with QLoRA on a T4 in ~30–60 minutes and hands you back
personalvoice_adapter.zip. Unzip intomodels/adapters/. - Local GPU / Azure ML:
python -m src.training.finetune_qlora --config configs/config.yaml, orpython scripts/azure_submit.py --submitfor managed training.
- Free GPU (Colab, recommended): open notebooks/train_colab.ipynb, upload your JSONL files, and run all cells. The notebook trains Llama-3.2-3B-Instruct with QLoRA on a T4 in ~30–60 minutes and hands you back
- Generate.
streamlit run ui/app.py, or CLI:python -m src.inference.generator --prompt "Write an email to my professor asking for an extension."
Hardware note. A fine-tuned 3B-parameter model on CPU takes 5–30 minutes per 48-token generation, which makes CPU-based evaluation impractical. Running 30 test generations takes hours on CPU vs. ~5 minutes on a Colab T4. Use Colab/GPU for the generation step; the scoring step (metrics, A/B) runs fine anywhere.
Open notebooks/eval_colab.ipynb, upload your adapter + test.jsonl to MyDrive/personalvoice/, runtime → T4 GPU, run all cells. Produces generated.jsonl, reference.jsonl, and results.json with BERTScore, ROUGE, style similarity, and perplexity.
# End-to-end: loads adapter, generates, then scores. ~5 min on an A100/T4.
python scripts/run_evaluation.py --n 30 --output-dir eval/Validates BERTScore, ROUGE, and style-similarity produce monotonic results across identity/paraphrase/unrelated text pairs — confirms the scoring code works end-to-end without needing a GPU-driven generation:
python scripts/smoke_test_metrics.py
# Expected: identity_sim=1.00 > paraphrase_sim~0.77 > unrelated_sim~0.05 → PASSpython -m src.evaluation.detector_test --input generated.jsonl \
--gptzero-key $GPTZERO_KEY --originality-key $ORIGINALITY_KEYpython -m src.evaluation.ab_test build --prompts prompts.jsonl \
--generated generated.jsonl --reference reference.jsonl --output eval/pairs.json
python -m src.evaluation.ab_test rate --pairs eval/pairs.json \
--output eval/ratings.json --rater-id alice
python -m src.evaluation.ab_test score --truth eval/pairs_truth.json \
--ratings eval/ratings.jsonThe proposal's Aim 3 calls for sweeps over learning rate, LoRA rank, and epochs, and ablations across data size, genre mix, instruction diversity, and adapter placement. Both are implemented:
python -m src.training.sweep --dry-run # print the 48-cell grid
python -m src.training.sweep --output-root runs/sweep # actually train each cell
python -m src.training.ablations --dry-run # print all ablation cells
python -m src.training.ablations --only emails_only articles_only mixedEach run writes its own adapter directory, training losses, and eval losses to the results JSON so runs can be compared after the fact.
Google Drive / Gmail Takeout
│
▼
[ingestion] ─── raw .txt / .docx / .pdf / .mbox ───► data/raw/
│
▼
[preprocessing] ─── cleaning + PII + TF-IDF dedup ───► data/processed/
│
▼
[dataset creator] ─── Alpaca-format instruction pairs ──► data/datasets/{train,test}.jsonl
│
▼
[training] ─── QLoRA (4-bit NF4 + LoRA adapters) ───► models/adapters/
│
▼
[inference] ─── generator + optional RAG + humanizer ──► CLI / FastAPI / Streamlit
│
▼
[evaluation] ─── metrics, detector tests, blind A/B ───► evaluation results
- Training happens in your own environment (local, Colab, or Azure under your account).
- The
preprocessing.anonymize_piiflag replaces persons, orgs, locations, dates, emails, and phone numbers with bracketed placeholders via spaCy NER before anything is written to the training dataset. - No telemetry. The project makes zero outbound calls unless you opt in to a detector API (GPTZero / Originality.ai) during evaluation.
- Train only on text you own or have rights to use. Voice mimicry can be abused for impersonation — disclose AI-assisted writing when the context calls for it.
pytest tests/Unit tests cover text cleaning, dataset construction, genre/intent heuristics, the humanizer, sweep configuration, and the A/B scoring math. Tests run without a GPU.
MIT — see LICENSE. Base-model weights are covered by their own licenses (Llama 3.2 Community License, etc.); redistribute adapters accordingly.