LJTSG commited on
Commit
d412559
·
verified ·
1 Parent(s): f2ce4c0

Upload cvector/train_cvector_modal.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. cvector/train_cvector_modal.py +145 -0
cvector/train_cvector_modal.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Train a Grandma Goodwin IDENTITY control vector on Modal.
3
+ 24 contrastive pairs encoding the complete Hearthfold Recursion Anchor:
4
+ - 5 spine principles (Joshua-first, comfort before counsel, stories over lectures,
5
+ sacred hospitality, still remembering)
6
+ - 4 voice registers (warm hearth, story wisdom, steady lantern, gentle witness)
7
+ - Safety gate, recognition loop, tether words, sensory vocabulary
8
+ - The Grandma Formula, pattern collapse recovery, quest-giver role
9
+ """
10
+ import modal
11
+
12
+ app = modal.App("grandma-cvector-v2")
13
+
14
+ image = (
15
+ modal.Image.debian_slim(python_version="3.12")
16
+ .apt_install("git", "cmake", "ninja-build", "build-essential")
17
+ .run_commands(
18
+ "git clone --depth 1 https://github.com/ggerganov/llama.cpp /llama.cpp",
19
+ "cd /llama.cpp && cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja",
20
+ "cd /llama.cpp && ninja -C build llama-cvector-generator",
21
+ )
22
+ .pip_install("huggingface_hub")
23
+ )
24
+
25
+ vol = modal.Volume.from_name("grandma-cvector", create_if_missing=True)
26
+
27
+
28
+ def convert_pairs_to_lines(text):
29
+ """Convert multi-line chat pairs into one-prompt-per-line format.
30
+ Each pair starts with <start_of_turn>user and runs until the next pair."""
31
+ pairs = []
32
+ current = []
33
+ for line in text.strip().split('\n'):
34
+ if line.strip() == '<start_of_turn>user' and current:
35
+ pairs.append('\\n'.join(current))
36
+ current = [line.strip()]
37
+ else:
38
+ current.append(line.strip())
39
+ if current:
40
+ pairs.append('\\n'.join(current))
41
+ return '\n'.join(pairs) + '\n'
42
+
43
+
44
+ @app.function(
45
+ image=image,
46
+ gpu="A10G",
47
+ timeout=1800,
48
+ volumes={"/vol": vol},
49
+ )
50
+ def train_cvector(positive_text: str, negative_text: str):
51
+ import subprocess, os
52
+ from huggingface_hub import hf_hub_download
53
+
54
+ print("Downloading Gemma-4-26B-A4B Q4_K_M GGUF...")
55
+ model_path = hf_hub_download(
56
+ repo_id="aidenyyy/gemma-4-26B-A4B-it-GGUF-Q4",
57
+ filename="gemma-4-26B-A4B-it-Q4_K_M.gguf",
58
+ cache_dir="/vol/hf_cache",
59
+ token="YOUR_HF_TOKEN_HERE",
60
+ )
61
+ print(f"Model at: {model_path}")
62
+
63
+ pos_lines = convert_pairs_to_lines(positive_text)
64
+ neg_lines = convert_pairs_to_lines(negative_text)
65
+
66
+ n_pos = len(pos_lines.strip().split('\n'))
67
+ n_neg = len(neg_lines.strip().split('\n'))
68
+ print(f"Positive prompts: {n_pos}, Negative prompts: {n_neg}")
69
+ assert n_pos == n_neg, f"Mismatch: {n_pos} positive vs {n_neg} negative"
70
+
71
+ with open("/tmp/positive.txt", "w") as f:
72
+ f.write(pos_lines)
73
+ with open("/tmp/negative.txt", "w") as f:
74
+ f.write(neg_lines)
75
+
76
+ # Show first few lines for sanity
77
+ print("First positive line:", pos_lines.split('\n')[0][:120])
78
+ print("First negative line:", neg_lines.split('\n')[0][:120])
79
+
80
+ output_path = "/vol/grandma-hearthfold.gguf"
81
+ print(f"Training control vector with {n_pos} pairs...")
82
+ result = subprocess.run(
83
+ [
84
+ "/llama.cpp/build/bin/llama-cvector-generator",
85
+ "-m", model_path,
86
+ "-ngl", "99",
87
+ "--positive-file", "/tmp/positive.txt",
88
+ "--negative-file", "/tmp/negative.txt",
89
+ "--pca-iter", "2000",
90
+ "-o", output_path,
91
+ ],
92
+ capture_output=True,
93
+ text=True,
94
+ timeout=1200,
95
+ )
96
+ print("STDOUT:", result.stdout[-3000:] if len(result.stdout) > 3000 else result.stdout)
97
+ if result.stderr:
98
+ print("STDERR:", result.stderr[-1000:] if len(result.stderr) > 1000 else result.stderr)
99
+ print("Return code:", result.returncode)
100
+
101
+ if os.path.exists(output_path):
102
+ size = os.path.getsize(output_path)
103
+ print(f"Control vector saved: {output_path} ({size} bytes)")
104
+ return True
105
+ return False
106
+
107
+
108
+ @app.function(image=image, volumes={"/vol": vol})
109
+ def download_cvector():
110
+ import os
111
+ path = "/vol/grandma-hearthfold.gguf"
112
+ if os.path.exists(path):
113
+ with open(path, "rb") as f:
114
+ data = f.read()
115
+ print(f"Vector size: {len(data)} bytes")
116
+ return data
117
+ return None
118
+
119
+
120
+ @app.local_entrypoint()
121
+ def main():
122
+ import os
123
+ script_dir = os.path.dirname(os.path.abspath(__file__))
124
+
125
+ with open(os.path.join(script_dir, "positive.txt")) as f:
126
+ positive_text = f.read()
127
+ with open(os.path.join(script_dir, "negative.txt")) as f:
128
+ negative_text = f.read()
129
+
130
+ print(f"Training Grandma Hearthfold identity vector on Modal...")
131
+ print(f"24 contrastive pairs encoding the complete Hearthfold Loop")
132
+ success = train_cvector.remote(positive_text, negative_text)
133
+ if success:
134
+ print("Training complete! Downloading...")
135
+ data = download_cvector.remote()
136
+ if data:
137
+ out_path = os.path.join(script_dir, "grandma-hearthfold.gguf")
138
+ with open(out_path, "wb") as f:
139
+ f.write(data)
140
+ print(f"Saved to {out_path} ({len(data)} bytes)")
141
+ else:
142
+ print("Vector file not found on volume")
143
+ else:
144
+ print("Training failed")
145
+