gowtham0992 Codex commited on
Commit
26c0a94
·
1 Parent(s): d729e70

Add Modal LoRA training launcher

Browse files

Co-authored-by: Codex <codex@openai.com>

FIELD_NOTES.md CHANGED
@@ -82,3 +82,9 @@ Deployment rule: publish or deploy a LoRA adapter only if it improves valid JSON
82
  Latest Discord discussion surfaced MiniCPM4.1 Transformers issues around SDPA attention masks and gibberish output. Jawbreaker now defaults `JAWBREAKER_ATTENTION_IMPLEMENTATION=eager` for the MiniCPM path, and keeps fallback analysis visible in the documentation.
83
 
84
  Guardrail: winning matters, but not by overclaiming. The project should not claim Well-Tuned, real user validation, Modal usage, or llama.cpp deployment unless those things are actually completed and documented.
 
 
 
 
 
 
 
82
  Latest Discord discussion surfaced MiniCPM4.1 Transformers issues around SDPA attention masks and gibberish output. Jawbreaker now defaults `JAWBREAKER_ATTENTION_IMPLEMENTATION=eager` for the MiniCPM path, and keeps fallback analysis visible in the documentation.
83
 
84
  Guardrail: winning matters, but not by overclaiming. The project should not claim Well-Tuned, real user validation, Modal usage, or llama.cpp deployment unless those things are actually completed and documented.
85
+
86
+ ## 2026-06-06 Modal Training Plan
87
+
88
+ Modal credits are the right place to run the MiniCPM LoRA job. Added `training/modal_train.py` so the same generated train/dev split can run on an A100 with outputs stored in a Modal volume.
89
+
90
+ Claim rule: Modal usage becomes a submission claim only after a real Modal run completes. Well-Tuned becomes a claim only after the resulting adapter is published and beats the base model or fallback on eval.
HONEST_SUBMISSION.md CHANGED
@@ -30,6 +30,7 @@ Jawbreaker should compete hard without gaming the hackathon.
30
  - `training/data/test.jsonl` and `eval/generated_eval.jsonl` are holdout data and should not be used for LoRA training.
31
  - A LoRA adapter should only be deployed if it improves valid JSON and safety metrics without increasing dangerous false negatives.
32
  - If synthetic training mostly teaches format-following, say that. Do not describe it as learning from real scam victims.
 
33
 
34
  ## Demo Rules
35
 
 
30
  - `training/data/test.jsonl` and `eval/generated_eval.jsonl` are holdout data and should not be used for LoRA training.
31
  - A LoRA adapter should only be deployed if it improves valid JSON and safety metrics without increasing dangerous false negatives.
32
  - If synthetic training mostly teaches format-following, say that. Do not describe it as learning from real scam victims.
33
+ - If Modal is used, include the Modal command, training output, and whether an adapter was actually published.
34
 
35
  ## Demo Rules
36
 
requirements-train.txt CHANGED
@@ -1,5 +1,6 @@
1
  accelerate==1.12.0
2
  datasets>=4.4.0,<5.0
 
3
  peft>=0.18.0,<1.0
4
  torch==2.9.1
5
  transformers==4.57.3
 
1
  accelerate==1.12.0
2
  datasets>=4.4.0,<5.0
3
+ modal>=1.0.0,<2.0
4
  peft>=0.18.0,<1.0
5
  torch==2.9.1
6
  transformers==4.57.3
training/README.md CHANGED
@@ -66,6 +66,39 @@ python3 training/train_lora.py \
66
  --output-dir training/output/jawbreaker-minicpm-lora
67
  ```
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  To publish the adapter for the Well-Tuned badge:
70
 
71
  ```bash
 
66
  --output-dir training/output/jawbreaker-minicpm-lora
67
  ```
68
 
69
+ ## Fine-Tune on Modal
70
+
71
+ Modal is the preferred training path because the hackathon gives Modal credits and MiniCPM4.1-8B should be trained on real GPU hardware, not inside the Hugging Face Space runtime.
72
+
73
+ One-time local setup:
74
+
75
+ ```bash
76
+ pip install modal
77
+ modal setup
78
+ ```
79
+
80
+ Create a Modal secret named `huggingface-secret` with an `HF_TOKEN` value that can read the base model and push to your Hugging Face account:
81
+
82
+ ```bash
83
+ modal secret create huggingface-secret HF_TOKEN=hf_...
84
+ ```
85
+
86
+ Run training on Modal:
87
+
88
+ ```bash
89
+ modal run training/modal_train.py
90
+ ```
91
+
92
+ Publish the adapter for the Well-Tuned badge only after eval says it is better:
93
+
94
+ ```bash
95
+ modal run training/modal_train.py \
96
+ --push-to-hub \
97
+ --hub-model-id gowtham0992/jawbreaker-minicpm-lora
98
+ ```
99
+
100
+ The Modal job writes checkpoints to the `jawbreaker-training` Modal volume under `/outputs`.
101
+
102
  To publish the adapter for the Well-Tuned badge:
103
 
104
  ```bash
training/modal_train.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import modal
6
+
7
+
8
+ APP_NAME = "jawbreaker-minicpm-lora"
9
+ REMOTE_ROOT = Path("/workspace")
10
+ REMOTE_OUTPUT = Path("/outputs")
11
+ LOCAL_ROOT = Path(__file__).resolve().parents[1]
12
+
13
+
14
+ image = (
15
+ modal.Image.debian_slim(python_version="3.12")
16
+ .pip_install(
17
+ "accelerate==1.12.0",
18
+ "datasets>=4.4.0,<5.0",
19
+ "huggingface-hub>=0.34.0,<1.0",
20
+ "peft>=0.18.0,<1.0",
21
+ "sentencepiece>=0.2.0,<1.0",
22
+ "torch==2.9.1",
23
+ "transformers==4.57.3",
24
+ )
25
+ .add_local_dir(LOCAL_ROOT / "jawbreaker", remote_path=REMOTE_ROOT / "jawbreaker")
26
+ .add_local_dir(LOCAL_ROOT / "training", remote_path=REMOTE_ROOT / "training")
27
+ .add_local_dir(LOCAL_ROOT / "eval", remote_path=REMOTE_ROOT / "eval")
28
+ )
29
+
30
+ app = modal.App(APP_NAME, image=image)
31
+ volume = modal.Volume.from_name("jawbreaker-training", create_if_missing=True)
32
+
33
+
34
+ @app.function(
35
+ gpu="A100",
36
+ timeout=6 * 60 * 60,
37
+ volumes={REMOTE_OUTPUT: volume},
38
+ secrets=[modal.Secret.from_name("huggingface-secret", required_keys=["HF_TOKEN"])],
39
+ )
40
+ def train_lora(
41
+ model_id: str = "openbmb/MiniCPM4.1-8B",
42
+ output_name: str = "jawbreaker-minicpm-lora",
43
+ epochs: float = 1.0,
44
+ push_to_hub: bool = False,
45
+ hub_model_id: str | None = None,
46
+ ) -> None:
47
+ import os
48
+ import subprocess
49
+
50
+ os.chdir(REMOTE_ROOT)
51
+ output_dir = REMOTE_OUTPUT / output_name
52
+ cmd = [
53
+ "python",
54
+ "training/train_lora.py",
55
+ "--model-id",
56
+ model_id,
57
+ "--train-file",
58
+ "training/data/train.jsonl",
59
+ "--dev-file",
60
+ "training/data/dev.jsonl",
61
+ "--output-dir",
62
+ str(output_dir),
63
+ "--epochs",
64
+ str(epochs),
65
+ ]
66
+ if push_to_hub:
67
+ cmd.append("--push-to-hub")
68
+ if hub_model_id:
69
+ cmd.extend(["--hub-model-id", hub_model_id])
70
+
71
+ subprocess.run(cmd, check=True)
72
+ volume.commit()
73
+
74
+
75
+ @app.local_entrypoint()
76
+ def main(
77
+ model_id: str = "openbmb/MiniCPM4.1-8B",
78
+ output_name: str = "jawbreaker-minicpm-lora",
79
+ epochs: float = 1.0,
80
+ push_to_hub: bool = False,
81
+ hub_model_id: str | None = None,
82
+ ) -> None:
83
+ train_lora.remote(
84
+ model_id=model_id,
85
+ output_name=output_name,
86
+ epochs=epochs,
87
+ push_to_hub=push_to_hub,
88
+ hub_model_id=hub_model_id,
89
+ )