essay-coach / code /scripts /upload.py
nonsodev's picture
code snapshot + README
e1679fa verified
Raw History Blame Contribute Delete
2.95 kB
"""Push finished runs from the Modal volume to Hugging Face (public, CC BY-NC-SA 4.0 like the data).
MODAL_PROFILE=labs3 modal run scripts/upload.py # scores-v1 parts-v1 band-v1
MODAL_PROFILE=labs3 modal run scripts/upload.py --runs scores-v2
"""
import modal
app = modal.App("essay-coach-upload")
vol = modal.Volume.from_name("essay-data")
image = modal.Image.debian_slim(python_version="3.11").pip_install("huggingface_hub")
HF_REPO = "nonsodev/essay-coach"
README = """---
license: cc-by-nc-sa-4.0
language: en
tags: [essay-scoring, argument-mining, deberta-v3, ielts]
---
# Essay coach
Three small models behind the Labs essay coach page: six analytic writing scores (ELLIPSE), argument parts with
effectiveness (PERSUADE 2.0), and a ridge band estimate fitted on human-banded IELTS essays.
- `runs/scores-v1`: deberta-v3-base regression head, MCRMSE 0.446 on the ELLIPSE test split (2,571 essays).
- `runs/parts-v1`: deberta-v3-base token model, BIO over 7 part types plus effectiveness; span F1 0.656 and
effectiveness log loss 0.478 on 2,000 held-out PERSUADE essays.
- `runs/band-v1`: `band.json`, ridge on the six scores + part counts + length; 5-fold CV RMSE 0.79 bands,
within half a band 67% of the time on 1,435 essays. An estimate, not a grade.
- `code/`: the training, decoding and serving code as of upload.
Trained on ELLIPSE and PERSUADE 2.0 (both CC BY-NC-SA 4.0), so these weights carry the same licence: share alike,
non-commercial. Backbone: microsoft/deberta-v3-base.
"""
@app.function(image=image, volumes={"/data": vol}, secrets=[modal.Secret.from_name("huggingface")], timeout=3600)
def upload(runs: list[str], code: dict[str, str]):
import os
import tempfile
from huggingface_hub import HfApi
api = HfApi()
api.create_repo(HF_REPO, exist_ok=True)
for tag in runs:
run = f"/data/runs/{tag}"
print(tag, sorted(os.listdir(run)), flush=True)
api.upload_folder(folder_path=run, repo_id=HF_REPO, path_in_repo=f"runs/{tag}", commit_message=f"upload {tag}")
with tempfile.TemporaryDirectory() as d:
for rel, text in code.items():
p = os.path.join(d, rel)
os.makedirs(os.path.dirname(p), exist_ok=True)
open(p, "w").write(text)
open(os.path.join(d, "README.md"), "w").write(README)
api.upload_folder(folder_path=d, repo_id=HF_REPO, path_in_repo="", commit_message="code snapshot + README")
print(f"done: https://huggingface.co/{HF_REPO}")
@app.local_entrypoint()
def main(runs: str = "scores-v1,parts-v1,band-v1"):
import glob
import os
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
code = {}
for pat in ("coach/*.py", "scripts/*.py", "web/*.html", "PLAN.md"):
for p in glob.glob(os.path.join(root, pat)):
code["code/" + os.path.relpath(p, root)] = open(p).read()
upload.remote(runs.split(","), code)