ameforge commited on
Commit
1674bed
·
verified ·
1 Parent(s): faf99ac

Add gearcut_infer.py

Browse files
Files changed (1) hide show
  1. gearcut_infer.py +82 -0
gearcut_infer.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GearCut — inference bridge.
3
+ Natural-language instruction (+ current timeline) -> editing operations (JSON).
4
+
5
+ Loads the trained gc_editor checkpoint and the GearCut tokenizer. If the files
6
+ aren't found locally, they are downloaded from the AMFORGE HuggingFace repos
7
+ (needs HF_TOKEN in the environment for private repos).
8
+ """
9
+ import os, json
10
+ import torch
11
+ import sentencepiece as spm
12
+ from gearcut_model import SparseMind, Config
13
+ from gearcut_ground import ground_operations
14
+
15
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
16
+
17
+
18
+ class GearCutModel:
19
+ def __init__(self,
20
+ checkpoint="gc_editor.pt",
21
+ tokenizer="gearcut_tok.model",
22
+ hf_repo_model="AMFORGE/gc_editor",
23
+ hf_repo_tok="AMFORGE/gearcut_tok",
24
+ hf_token=None):
25
+ tok_path, ckpt_path = tokenizer, checkpoint
26
+ if not os.path.exists(tok_path) or not os.path.exists(ckpt_path):
27
+ from huggingface_hub import hf_hub_download
28
+ token = hf_token or os.environ.get("HF_TOKEN")
29
+ if not os.path.exists(tok_path):
30
+ tok_path = hf_hub_download(hf_repo_tok, "gearcut_tok.model", token=token)
31
+ if not os.path.exists(ckpt_path):
32
+ ckpt_path = hf_hub_download(hf_repo_model, "gc_editor.pt", token=token)
33
+
34
+ self.sp = spm.SentencePieceProcessor(); self.sp.Load(tok_path)
35
+
36
+ ckpt = torch.load(ckpt_path, map_location=DEVICE, weights_only=False)
37
+ fields = set(Config.__dataclass_fields__)
38
+ self.cfg = Config(**{k: v for k, v in ckpt["config"].items() if k in fields})
39
+ self.model = SparseMind(self.cfg).to(DEVICE)
40
+ self.model.load_state_dict(ckpt["model"])
41
+ self.model.eval()
42
+ print(f"gc_editor loaded ({self.model.n_params:,} params, vocab={self.cfg.vocab_size})")
43
+
44
+ @staticmethod
45
+ def context(clips):
46
+ # clips: list of (id, name, in, out) -> "clips: c1=name(0.0-8.0) c2=..."
47
+ return "clips: " + " ".join(
48
+ f"{cid}={name}({i:.1f}-{o:.1f})" for cid, name, i, o in clips)
49
+
50
+ @torch.no_grad()
51
+ def operations(self, instruction, clips, max_new=160):
52
+ """Return (ops, repairs, raw). ops is grounded against the instruction;
53
+ repairs lists what the safety layer changed/rejected; ops is None if JSON failed."""
54
+ prompt = f"{self.context(clips)} | {instruction} =>"
55
+ ids = torch.tensor([self.sp.EncodeAsIds(prompt)], device=DEVICE)
56
+ out = self.model.generate(ids, max_new=max_new, temp=1.0, top_k=1)
57
+ gen_ids = out[0, ids.shape[1]:].tolist()
58
+ eos = self.cfg.eos_id
59
+ if eos in gen_ids:
60
+ gen_ids = gen_ids[:gen_ids.index(eos)]
61
+ text = self.sp.DecodeIds(gen_ids).strip()
62
+ if "[" in text and "]" in text:
63
+ text = text[text.index("["): text.rindex("]") + 1]
64
+ try:
65
+ raw_ops = json.loads(text)
66
+ except Exception:
67
+ return None, [], text
68
+ clip_ids = [c[0] for c in clips]
69
+ grounded, repairs = ground_operations(raw_ops, instruction, clip_ids)
70
+ return grounded, repairs, text
71
+
72
+
73
+ if __name__ == "__main__":
74
+ m = GearCutModel()
75
+ demo_clips = [("c1", "intro.mp4", 0.0, 8.0)]
76
+ for instr in ["remove the first 3 seconds of the clip",
77
+ "keep only 2 to 6 of clip 1",
78
+ "export as final.mp4 in tiktok_vertical"]:
79
+ ops, repairs, raw = m.operations(instr, demo_clips)
80
+ print(f"\n> {instr}\n {ops if ops is not None else '[parse failed] ' + raw}")
81
+ for r in repairs:
82
+ print(" ~", r)