File size: 5,024 Bytes
d412559 | 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 | """
Train a Grandma Goodwin IDENTITY control vector on Modal.
24 contrastive pairs encoding the complete Hearthfold Recursion Anchor:
- 5 spine principles (Joshua-first, comfort before counsel, stories over lectures,
sacred hospitality, still remembering)
- 4 voice registers (warm hearth, story wisdom, steady lantern, gentle witness)
- Safety gate, recognition loop, tether words, sensory vocabulary
- The Grandma Formula, pattern collapse recovery, quest-giver role
"""
import modal
app = modal.App("grandma-cvector-v2")
image = (
modal.Image.debian_slim(python_version="3.12")
.apt_install("git", "cmake", "ninja-build", "build-essential")
.run_commands(
"git clone --depth 1 https://github.com/ggerganov/llama.cpp /llama.cpp",
"cd /llama.cpp && cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja",
"cd /llama.cpp && ninja -C build llama-cvector-generator",
)
.pip_install("huggingface_hub")
)
vol = modal.Volume.from_name("grandma-cvector", create_if_missing=True)
def convert_pairs_to_lines(text):
"""Convert multi-line chat pairs into one-prompt-per-line format.
Each pair starts with <start_of_turn>user and runs until the next pair."""
pairs = []
current = []
for line in text.strip().split('\n'):
if line.strip() == '<start_of_turn>user' and current:
pairs.append('\\n'.join(current))
current = [line.strip()]
else:
current.append(line.strip())
if current:
pairs.append('\\n'.join(current))
return '\n'.join(pairs) + '\n'
@app.function(
image=image,
gpu="A10G",
timeout=1800,
volumes={"/vol": vol},
)
def train_cvector(positive_text: str, negative_text: str):
import subprocess, os
from huggingface_hub import hf_hub_download
print("Downloading Gemma-4-26B-A4B Q4_K_M GGUF...")
model_path = hf_hub_download(
repo_id="aidenyyy/gemma-4-26B-A4B-it-GGUF-Q4",
filename="gemma-4-26B-A4B-it-Q4_K_M.gguf",
cache_dir="/vol/hf_cache",
token="YOUR_HF_TOKEN_HERE",
)
print(f"Model at: {model_path}")
pos_lines = convert_pairs_to_lines(positive_text)
neg_lines = convert_pairs_to_lines(negative_text)
n_pos = len(pos_lines.strip().split('\n'))
n_neg = len(neg_lines.strip().split('\n'))
print(f"Positive prompts: {n_pos}, Negative prompts: {n_neg}")
assert n_pos == n_neg, f"Mismatch: {n_pos} positive vs {n_neg} negative"
with open("/tmp/positive.txt", "w") as f:
f.write(pos_lines)
with open("/tmp/negative.txt", "w") as f:
f.write(neg_lines)
# Show first few lines for sanity
print("First positive line:", pos_lines.split('\n')[0][:120])
print("First negative line:", neg_lines.split('\n')[0][:120])
output_path = "/vol/grandma-hearthfold.gguf"
print(f"Training control vector with {n_pos} pairs...")
result = subprocess.run(
[
"/llama.cpp/build/bin/llama-cvector-generator",
"-m", model_path,
"-ngl", "99",
"--positive-file", "/tmp/positive.txt",
"--negative-file", "/tmp/negative.txt",
"--pca-iter", "2000",
"-o", output_path,
],
capture_output=True,
text=True,
timeout=1200,
)
print("STDOUT:", result.stdout[-3000:] if len(result.stdout) > 3000 else result.stdout)
if result.stderr:
print("STDERR:", result.stderr[-1000:] if len(result.stderr) > 1000 else result.stderr)
print("Return code:", result.returncode)
if os.path.exists(output_path):
size = os.path.getsize(output_path)
print(f"Control vector saved: {output_path} ({size} bytes)")
return True
return False
@app.function(image=image, volumes={"/vol": vol})
def download_cvector():
import os
path = "/vol/grandma-hearthfold.gguf"
if os.path.exists(path):
with open(path, "rb") as f:
data = f.read()
print(f"Vector size: {len(data)} bytes")
return data
return None
@app.local_entrypoint()
def main():
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(script_dir, "positive.txt")) as f:
positive_text = f.read()
with open(os.path.join(script_dir, "negative.txt")) as f:
negative_text = f.read()
print(f"Training Grandma Hearthfold identity vector on Modal...")
print(f"24 contrastive pairs encoding the complete Hearthfold Loop")
success = train_cvector.remote(positive_text, negative_text)
if success:
print("Training complete! Downloading...")
data = download_cvector.remote()
if data:
out_path = os.path.join(script_dir, "grandma-hearthfold.gguf")
with open(out_path, "wb") as f:
f.write(data)
print(f"Saved to {out_path} ({len(data)} bytes)")
else:
print("Vector file not found on volume")
else:
print("Training failed")
|