Spaces:
Runtime error
Runtime error
tc043 commited on
Commit ·
8207f22
1
Parent(s): 0b80cf0
feat: implement Unsloth training pipeline with Modal volumes and update documentation on model persona mitigation
Browse files- README.md +7 -0
- modal_train.py +63 -11
- test_modal_mount.py +2 -0
README.md
CHANGED
|
@@ -65,3 +65,10 @@ modal run modal_train.py
|
|
| 65 |
```
|
| 66 |
|
| 67 |
`modal_train.py` is currently a scaffold for the hackathon story. The remaining production step is to wire `verbs.jsonl` into Modal storage, uncomment the `SFTTrainer` block, run the LoRA job, and export/publish the GGUF or adapter artifact.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
```
|
| 66 |
|
| 67 |
`modal_train.py` is currently a scaffold for the hackathon story. The remaining production step is to wire `verbs.jsonl` into Modal storage, uncomment the `SFTTrainer` block, run the LoRA job, and export/publish the GGUF or adapter artifact.
|
| 68 |
+
|
| 69 |
+
## Known Limitations & Model Trade-offs
|
| 70 |
+
|
| 71 |
+
During semantic stress testing of the live backend, we discovered the classic "Alignment Tax" associated with small, 4B-parameter local models:
|
| 72 |
+
* **The Problem:** Highly instruction-tuned base models (like Nemotron-Mini and MiniCPM) are deeply trained to act like "helpful conversational assistants." When prompted with complex goals like *"write a python script"*, they often break out of the Cognitive Pacemaker persona and output helpful paragraphs instead of strict 8-word physical atomic actions.
|
| 73 |
+
* **Current Mitigation:** We employ aggressive few-shot prompting, manual Jinja template overrides, and Python-side text post-processing to brutally truncate their output.
|
| 74 |
+
* **The Ultimate Fix (Roadmap):** Instead of changing to a larger, slower 8B+ model, we plan to fully execute the `modal_train.py` Unsloth fine-tuning pipeline. By training a LoRA adapter specifically on `verbs.jsonl`, we will permanently "lobotomize" the chatbot persona, forcing the model to strictly output imperative verbs natively.
|
modal_train.py
CHANGED
|
@@ -12,16 +12,24 @@ image = (
|
|
| 12 |
"accelerate",
|
| 13 |
"bitsandbytes"
|
| 14 |
)
|
|
|
|
| 15 |
)
|
| 16 |
|
| 17 |
app = modal.App("step-zero-finetune")
|
|
|
|
| 18 |
|
| 19 |
-
@app.function(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
def train_model():
|
| 21 |
-
from unsloth import FastLanguageModel
|
| 22 |
from datasets import load_dataset
|
| 23 |
from trl import SFTTrainer
|
| 24 |
from transformers import TrainingArguments
|
|
|
|
| 25 |
|
| 26 |
print("Loading Base Model: Nemotron-Mini-4B...")
|
| 27 |
max_seq_length = 1024
|
|
@@ -30,7 +38,7 @@ def train_model():
|
|
| 30 |
model_name = "nvidia/Nemotron-Mini-4B-Instruct",
|
| 31 |
max_seq_length = max_seq_length,
|
| 32 |
dtype = None,
|
| 33 |
-
load_in_4bit = True,
|
| 34 |
)
|
| 35 |
|
| 36 |
model = FastLanguageModel.get_peft_model(
|
|
@@ -46,21 +54,65 @@ def train_model():
|
|
| 46 |
use_rslora = False,
|
| 47 |
)
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
print("Simulating dataset load...")
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
print("Starting LoRA fine-tuning for Syntactic Enforcement...")
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
print("Exporting to GGUF...")
|
| 58 |
-
|
| 59 |
|
| 60 |
-
print("Training complete. GGUF artifact generated.")
|
| 61 |
|
| 62 |
@app.local_entrypoint()
|
| 63 |
def main():
|
| 64 |
print("Submitting training job to Modal infrastructure...")
|
| 65 |
train_model.remote()
|
| 66 |
-
print("Done!
|
|
|
|
|
|
| 12 |
"accelerate",
|
| 13 |
"bitsandbytes"
|
| 14 |
)
|
| 15 |
+
.add_local_file("verbs.jsonl", "/root/verbs.jsonl")
|
| 16 |
)
|
| 17 |
|
| 18 |
app = modal.App("step-zero-finetune")
|
| 19 |
+
vol = modal.Volume.from_name("step-zero-volume", create_if_missing=True)
|
| 20 |
|
| 21 |
+
@app.function(
|
| 22 |
+
image=image,
|
| 23 |
+
gpu="A10G",
|
| 24 |
+
timeout=1800,
|
| 25 |
+
volumes={"/vol": vol}
|
| 26 |
+
)
|
| 27 |
def train_model():
|
| 28 |
+
from unsloth import FastLanguageModel, is_bfloat16_supported
|
| 29 |
from datasets import load_dataset
|
| 30 |
from trl import SFTTrainer
|
| 31 |
from transformers import TrainingArguments
|
| 32 |
+
import json
|
| 33 |
|
| 34 |
print("Loading Base Model: Nemotron-Mini-4B...")
|
| 35 |
max_seq_length = 1024
|
|
|
|
| 38 |
model_name = "nvidia/Nemotron-Mini-4B-Instruct",
|
| 39 |
max_seq_length = max_seq_length,
|
| 40 |
dtype = None,
|
| 41 |
+
load_in_4bit = True,
|
| 42 |
)
|
| 43 |
|
| 44 |
model = FastLanguageModel.get_peft_model(
|
|
|
|
| 54 |
use_rslora = False,
|
| 55 |
)
|
| 56 |
|
| 57 |
+
print("Loading and formatting verbs.jsonl dataset...")
|
| 58 |
+
dataset = load_dataset("json", data_files="/root/verbs.jsonl", split="train")
|
|
|
|
| 59 |
|
| 60 |
+
def format_prompt(examples):
|
| 61 |
+
texts = []
|
| 62 |
+
for instruction, output in zip(examples["instruction"], examples["output"]):
|
| 63 |
+
try:
|
| 64 |
+
task = json.loads(output)["task"]
|
| 65 |
+
except:
|
| 66 |
+
task = output
|
| 67 |
+
|
| 68 |
+
parts = instruction.split(". Failures: ")
|
| 69 |
+
goal = parts[0].replace("Goal: ", "").strip()
|
| 70 |
+
failures = parts[1].split(".")[0].strip()
|
| 71 |
+
|
| 72 |
+
system_msg = "You are a cognitive pacemaker. Break down goals into extremely tiny, atomic physical actions under 8 words."
|
| 73 |
+
prompt = f"<extra_id_0>System\n{system_msg}\n\n"
|
| 74 |
+
prompt += f"<extra_id_1>User\nGoal: {goal}\nCompleted Tasks: None\nFailures: {failures}\nOutput the NEXT step.\n"
|
| 75 |
+
prompt += f"<extra_id_1>Assistant\n{task}\n"
|
| 76 |
+
texts.append(prompt)
|
| 77 |
+
return {"text": texts}
|
| 78 |
+
|
| 79 |
+
dataset = dataset.map(format_prompt, batched=True)
|
| 80 |
+
|
| 81 |
print("Starting LoRA fine-tuning for Syntactic Enforcement...")
|
| 82 |
+
trainer = SFTTrainer(
|
| 83 |
+
model = model,
|
| 84 |
+
tokenizer = tokenizer,
|
| 85 |
+
train_dataset = dataset,
|
| 86 |
+
dataset_text_field = "text",
|
| 87 |
+
max_seq_length = max_seq_length,
|
| 88 |
+
dataset_num_proc = 2,
|
| 89 |
+
packing = False,
|
| 90 |
+
args = TrainingArguments(
|
| 91 |
+
per_device_train_batch_size = 2,
|
| 92 |
+
gradient_accumulation_steps = 4,
|
| 93 |
+
warmup_steps = 5,
|
| 94 |
+
max_steps = 60,
|
| 95 |
+
learning_rate = 2e-4,
|
| 96 |
+
fp16 = not is_bfloat16_supported(),
|
| 97 |
+
bf16 = is_bfloat16_supported(),
|
| 98 |
+
logging_steps = 1,
|
| 99 |
+
optim = "adamw_8bit",
|
| 100 |
+
weight_decay = 0.01,
|
| 101 |
+
lr_scheduler_type = "linear",
|
| 102 |
+
seed = 3407,
|
| 103 |
+
output_dir = "outputs",
|
| 104 |
+
),
|
| 105 |
+
)
|
| 106 |
+
trainer.train()
|
| 107 |
|
| 108 |
print("Exporting to GGUF...")
|
| 109 |
+
model.save_pretrained_gguf("/vol/step-zero-nemotron", tokenizer, quantization_method = "q4_k_m")
|
| 110 |
|
| 111 |
+
print("Training complete. GGUF artifact generated in /vol.")
|
| 112 |
|
| 113 |
@app.local_entrypoint()
|
| 114 |
def main():
|
| 115 |
print("Submitting training job to Modal infrastructure...")
|
| 116 |
train_model.remote()
|
| 117 |
+
print("Done! To download your weights, run:")
|
| 118 |
+
print("modal volume get step-zero-volume step-zero-nemotron-unsloth-Q4_K_M.gguf models/")
|
test_modal_mount.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import modal
|
| 2 |
+
print(hasattr(modal.Image, 'add_local_file'))
|