File size: 5,676 Bytes
dd82460
 
 
37aa665
 
 
 
 
 
 
 
 
 
 
8207f22
dd82460
 
 
8207f22
dd82460
8207f22
 
 
 
 
 
dd82460
8207f22
dd82460
 
 
8207f22
dd82460
 
 
 
 
 
 
 
5a8eee8
dd82460
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8207f22
 
dd82460
8207f22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd82460
8207f22
 
 
 
 
 
 
 
 
 
 
 
913be0b
 
8207f22
 
 
 
 
913be0b
8207f22
 
3f0b366
8207f22
 
 
dd82460
5a8eee8
 
 
 
dd82460
5a8eee8
 
 
 
eac67fe
5a8eee8
 
 
 
 
 
 
 
 
 
66b1bc1
 
5a8eee8
 
 
 
 
 
 
 
 
 
 
 
 
 
eac67fe
 
 
 
 
8207f22
dd82460
 
 
 
 
8207f22
eac67fe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import modal

# Define the environment with Unsloth pre-installed
image = (
    modal.Image.from_registry("nvidia/cuda:12.1.1-devel-ubuntu22.04", add_python="3.10")
    .apt_install("git")
    .pip_install(
        "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git",
        "xformers",
        "trl",
        "peft",
        "accelerate",
        "bitsandbytes"
    )
    .add_local_file("verbs.jsonl", "/root/verbs.jsonl")
)

app = modal.App("step-zero-finetune")
vol = modal.Volume.from_name("step-zero-volume", create_if_missing=True)

@app.function(
    image=image, 
    gpu="A10G", 
    timeout=1800,
    volumes={"/vol": vol}
)
def train_model():
    from unsloth import FastLanguageModel, is_bfloat16_supported
    from datasets import load_dataset
    from trl import SFTTrainer
    from transformers import TrainingArguments
    import json

    print("Loading Base Model: Nemotron-Mini-4B...")
    max_seq_length = 1024
    
    model, tokenizer = FastLanguageModel.from_pretrained(
        model_name = "nvidia/Nemotron-Mini-4B-Instruct",
        max_seq_length = max_seq_length,
        dtype = None,
        load_in_4bit = False,
    )

    model = FastLanguageModel.get_peft_model(
        model,
        r = 16,
        target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                          "gate_proj", "up_proj", "down_proj",],
        lora_alpha = 16,
        lora_dropout = 0,
        bias = "none",
        use_gradient_checkpointing = "unsloth",
        random_state = 3407,
        use_rslora = False,
    )

    print("Loading and formatting verbs.jsonl dataset...")
    dataset = load_dataset("json", data_files="/root/verbs.jsonl", split="train")
    
    def format_prompt(examples):
        texts = []
        for instruction, output in zip(examples["instruction"], examples["output"]):
            try:
                task = json.loads(output)["task"]
            except:
                task = output
                
            parts = instruction.split(". Failures: ")
            goal = parts[0].replace("Goal: ", "").strip()
            failures = parts[1].split(".")[0].strip()
            
            system_msg = "You are a cognitive pacemaker. Break down goals into extremely tiny, atomic physical actions under 8 words."
            prompt = f"<extra_id_0>System\n{system_msg}\n\n"
            prompt += f"<extra_id_1>User\nGoal: {goal}\nCompleted Tasks: None\nFailures: {failures}\nOutput the NEXT step.\n"
            prompt += f"<extra_id_1>Assistant\n{task}\n"
            texts.append(prompt)
        return {"text": texts}

    dataset = dataset.map(format_prompt, batched=True)

    print("Starting LoRA fine-tuning for Syntactic Enforcement...")
    trainer = SFTTrainer(
        model = model,
        tokenizer = tokenizer,
        train_dataset = dataset,
        dataset_text_field = "text",
        max_seq_length = max_seq_length,
        dataset_num_proc = 2,
        packing = False,
        args = TrainingArguments(
            per_device_train_batch_size = 2,
            gradient_accumulation_steps = 4,
            warmup_steps = 5,
            max_steps = 70,
            learning_rate = 1e-4,
            fp16 = not is_bfloat16_supported(),
            bf16 = is_bfloat16_supported(),
            logging_steps = 1,
            optim = "adamw_8bit",
            weight_decay = 0.01,
            lr_scheduler_type = "cosine",
            seed = 3407,
            output_dir = "outputs",
            save_strategy = "no",
        ),
    )
    trainer.train()
    
    print("Merging LoRA adapter into base model...")
    import os, subprocess
    hf_dir = "/root/step-zero-nemotron-hf"
    os.makedirs(hf_dir, exist_ok=True)
    
    # Merge the LoRA weights into the base model using PEFT directly
    merged_model = model.merge_and_unload()
    merged_model.save_pretrained(hf_dir)
    tokenizer.save_pretrained(hf_dir)
    
    print(f"HF export contents: {os.listdir(hf_dir)}")
    
    print("Cloning llama.cpp for manual GGUF conversion...")
    subprocess.run(["git", "clone", "--depth", "1", "https://github.com/ggerganov/llama.cpp.git", "/root/llama.cpp"], check=True)
    subprocess.run(["pip", "install", "-r", "/root/llama.cpp/requirements.txt"], check=True)
    
    print("Converting HF model to GGUF...")
    result = subprocess.run([
        "python3", "/root/llama.cpp/convert_hf_to_gguf.py", 
        hf_dir, 
        "--outfile", "/vol/step-zero-nemotron-finetuned.gguf", 
        "--outtype", "q8_0"
    ], capture_output=True, text=True)
    print("STDOUT:", result.stdout[-2000:] if result.stdout else "")
    print("STDERR:", result.stderr[-2000:] if result.stderr else "")
    if result.returncode != 0:
        print(f"convert_hf_to_gguf.py failed with code {result.returncode}")
        print("Trying f16 fallback...")
        result2 = subprocess.run([
            "python3", "/root/llama.cpp/convert_hf_to_gguf.py", 
            hf_dir, 
            "--outfile", "/vol/step-zero-nemotron-finetuned.gguf", 
            "--outtype", "f16"
        ], capture_output=True, text=True)
        print("STDOUT:", result2.stdout[-2000:] if result2.stdout else "")
        print("STDERR:", result2.stderr[-2000:] if result2.stderr else "")
    
    try:
        vol.commit()
    except Exception as e:
        print("vol.commit() skipped:", e)
    print("Training complete. GGUF artifact generated in /vol.")

@app.local_entrypoint()
def main():
    print("Submitting training job to Modal infrastructure...")
    train_model.remote()
    print("Done! To download your weights, run:")
    print("modal volume get step-zero-volume step-zero-nemotron.gguf models/")