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
Files changed (3) hide show
  1. README.md +7 -0
  2. modal_train.py +63 -11
  3. 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(image=image, gpu="A10G", timeout=1800)
 
 
 
 
 
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, # Crucial for speed and fitting on consumer GPUs later
34
  )
35
 
36
  model = FastLanguageModel.get_peft_model(
@@ -46,21 +54,65 @@ def train_model():
46
  use_rslora = False,
47
  )
48
 
49
- # Note: Ensure you uploaded 'verbs.jsonl' to a Modal Volume, or fetch it via URL in real implementation
50
- # dataset = load_dataset("json", data_files="verbs.jsonl", split="train")
51
- print("Simulating dataset load...")
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  print("Starting LoRA fine-tuning for Syntactic Enforcement...")
54
- # trainer = SFTTrainer( ... )
55
- # trainer.train()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
  print("Exporting to GGUF...")
58
- # model.save_pretrained_gguf("step-zero-nemotron", tokenizer, quantization_method = "q4_k_m")
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! Download your weights from your Modal storage.")
 
 
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'))