Skip to content

Commit 9303036

Browse files
Merge pull request #568 from gittensor-ai-lab/fix/copycat-func-fp-main-helpers
fix(copycat): skip main-shared tiny helpers in per-function layer
2 parents 1031e09 + 51acf26 commit 9303036

5 files changed

Lines changed: 197 additions & 53 deletions

File tree

.github/COPYCATS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ Shared thresholds live in `eval/copycat_policy.py`.
2929
`copycats.json` with `"blocked": false, "penalty_days": 0` and are **skipped** by both guards.
3030
- **Tiny PRs** (< 15 added lines) are skipped unless **≥ 98%** literal overlap.
3131
- **Per-function check**: a single CUDA function ≥ **92%** contained in an earlier PR → **warn only**
32-
(never block on per-function alone). CUDA launch / template-instantiation boilerplate is excluded.
32+
(never block on per-function alone). Skips: CUDA launch boilerplate, tiny `__device__` helpers
33+
(≤20 lines / <80 tokens), and blocks whose lines already exist on `origin/main` for that file
34+
(PR #566-style false positives on shared dequant helpers). Requires PR-level overlap ≥ **15%**.
3335
**Block requires PR-level containment ≥ 85%.**
3436
- **Structural similarity** and **LLM auto-warn** are **disabled** by default (too many false positives when independent contributors land similar optimizations).
3537

eval/copycat_guard.py

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from copycat_policy import (
2121
COPYCAT_BLOCK, COPYCAT_WARN, COPYCAT_CONTAINMENT, MAX_WARNINGS,
2222
MIN_ADDED_LINES, LITERAL_BLOCK, FUNC_BLOCK_WARN,
23+
FUNC_MIN_BODY_TOKENS, FUNC_MIN_PR_LEVEL, FUNC_MAIN_SKIP,
2324
STRUCTURAL_ENABLED, LLM_ENABLED, skip_copycat_scoring,
2425
COPYCAT_REFERENCE_STATE,
2526
)
@@ -70,16 +71,65 @@ def gh(args):
7071

7172

7273
def is_boilerplate_block(sig, body):
73-
"""Skip per-function scoring on shared launch/dispatch boilerplate."""
74+
"""Skip per-function scoring on shared launch/dispatch / tiny device helpers."""
7475
text = f"{sig} {body}".strip()
7576
if _BOILERPLATE_RE.search(text) and len(body.splitlines()) <= 3:
7677
return True
7778
s = sig.strip()
7879
if s.startswith(("if ", "if(", "} else", "else if")):
7980
return True
81+
nlines = sum(1 for l in body.splitlines() if l.strip())
82+
# Tiny __device__ helpers (dequant/scale) converge across MoE PRs and often
83+
# already exist on main — not evidence of copying another open PR.
84+
if "__device__" in s and nlines <= 20:
85+
return True
86+
if nlines <= 8:
87+
return True
88+
tokens = [t for l in body.splitlines() for t in l.split() if len(t) > 1]
89+
if len(tokens) < FUNC_MIN_BODY_TOKENS:
90+
return True
8091
return False
8192

8293

94+
def _main_file_text(repo, path, cache):
95+
"""Raw file contents at origin/main (cached). Empty if missing."""
96+
if not path or path in cache:
97+
return cache.get(path, "")
98+
r = gh(["api", f"repos/{repo}/contents/{path}?ref=main",
99+
"-H", "Accept: application/vnd.github.raw"])
100+
text = r.stdout or "" if r.returncode == 0 else ""
101+
cache[path] = text
102+
return text
103+
104+
105+
def block_already_on_main(repo, path, body, cache=None):
106+
"""True when ≥FUNC_MAIN_SKIP of the block's non-empty lines already exist on main."""
107+
if cache is None:
108+
cache = {}
109+
main = _main_file_text(repo, path, cache)
110+
if not main:
111+
return False
112+
lines = [l.strip() for l in body.splitlines() if l.strip()]
113+
if not lines:
114+
return False
115+
main_lines = {l.strip() for l in main.splitlines() if l.strip()}
116+
hit = sum(1 for l in lines if l in main_lines)
117+
return (hit / len(lines)) >= FUNC_MAIN_SKIP
118+
119+
120+
def func_layer_should_warn(pr_level_c, func_c, copy_sig, copy_body, path="", repo="", cache=None):
121+
"""Gate L4 warns — skip tiny/main-shared helpers and negligible PR-level overlap."""
122+
if func_c < FUNC_BLOCK_WARN:
123+
return False
124+
if pr_level_c < FUNC_MIN_PR_LEVEL:
125+
return False
126+
if is_boilerplate_block(copy_sig, copy_body):
127+
return False
128+
if repo and path and block_already_on_main(repo, path, copy_body, cache):
129+
return False
130+
return True
131+
132+
83133
def pr_has_label(repo, num, label):
84134
info = json.loads(gh(["pr", "view", str(num), "-R", repo, "--json", "labels"]).stdout or "{}")
85135
return any(l.get("name") == label for l in info.get("labels", []))
@@ -166,12 +216,19 @@ def structural_similarity(repo, copy_num, orig_num, containment_pct):
166216

167217
def split_into_blocks(repo, num):
168218
"""Split a PR's added lines into logical CUDA code blocks (kernel functions, device
169-
functions, and other named scopes). Filters out trivial if/else conditionals that
170-
happen to match across PRs but aren't actual functions (minimum 10 tokens)."""
219+
functions, and other named scopes). Returns list of (sig, body, path). Filters out
220+
trivial if/else conditionals that happen to match across PRs but aren't actual
221+
functions (minimum 10 tokens)."""
171222
diff = gh(["pr", "diff", str(num), "-R", repo]).stdout or ""
172223
blocks = []
173-
current_sig = None; current_body = []
224+
current_sig = None; current_body = []; current_file = ""
174225
for line in diff.splitlines():
226+
if line.startswith("+++ "):
227+
p = line[4:].strip()
228+
if p.startswith("b/"):
229+
p = p[2:]
230+
current_file = "" if (not p or p == "/dev/null") else p
231+
continue
175232
if not line.startswith("+") or line.startswith("+++"):
176233
continue
177234
s = line[1:].strip()
@@ -190,34 +247,37 @@ def split_into_blocks(repo, num):
190247
if current_sig and current_body:
191248
tokens = [t for l in current_body for t in l.split() if len(t)>1]
192249
if len(tokens) >= 10:
193-
blocks.append((current_sig, "\n".join(current_body)))
250+
blocks.append((current_sig, "\n".join(current_body), current_file))
194251
current_sig = s
195252
current_body = []
196253
elif current_sig is not None:
197254
current_body.append(line[1:])
198255
if current_sig and current_body:
199256
tokens = [t for l in current_body for t in l.split() if len(t)>1]
200257
if len(tokens) >= 10:
201-
blocks.append((current_sig, "\n".join(current_body)))
258+
blocks.append((current_sig, "\n".join(current_body), current_file))
202259
return blocks
203260

204261

205-
def per_function_containment(repo, copy_num, orig_num):
206-
"""Return the HIGHEST per-function containment across all shared blocks. If the original
207-
PR has one function (focused change) and the copy PR adds it inside a larger PR, this
208-
will catch it even when PR-level containment is low."""
262+
def per_function_containment(repo, copy_num, orig_num, main_cache=None):
263+
"""Return (best_c, copy_sig, orig_sig, copy_body, path) for the strongest non-boilerplate
264+
shared block. Skips tiny device helpers and blocks already present on main."""
265+
if main_cache is None:
266+
main_cache = {}
209267
copy_blocks = split_into_blocks(repo, copy_num)
210268
orig_blocks = split_into_blocks(repo, orig_num)
211269
if not copy_blocks or not orig_blocks:
212-
return 0.0, "", ""
213-
best = 0.0; best_copy_sig = ""; best_orig_sig = ""
214-
for csig, cb in copy_blocks:
270+
return 0.0, "", "", "", ""
271+
best = 0.0; best_copy_sig = ""; best_orig_sig = ""; best_body = ""; best_path = ""
272+
for csig, cb, cpath in copy_blocks:
215273
if is_boilerplate_block(csig, cb):
216274
continue
275+
if block_already_on_main(repo, cpath, cb, main_cache):
276+
continue
217277
ctokens = set(cb.split())
218278
if len(ctokens) < 5:
219279
continue
220-
for osig, ob in orig_blocks:
280+
for osig, ob, _opath in orig_blocks:
221281
otokens = set(ob.split())
222282
if len(otokens) < 5:
223283
continue
@@ -226,7 +286,8 @@ def per_function_containment(repo, copy_num, orig_num):
226286
c = len(ctokens & otokens) / len(ctokens)
227287
if c > best:
228288
best = c; best_copy_sig = csig; best_orig_sig = osig
229-
return best, best_copy_sig, best_orig_sig
289+
best_body = cb; best_path = cpath
290+
return best, best_copy_sig, best_orig_sig, best_body, best_path
230291

231292

232293
def _llm_provider():
@@ -420,10 +481,10 @@ def warn_copycat(repo, num, original, author, strike_count, containment_pct, str
420481
f"is substantially contained in #{original} by a different author — the PR-level "
421482
f"containment is low because the copied function is embedded inside a larger diff.")
422483
elif structural:
423-
head = (f"**{containment_pct:.0f}% containment** + structural similarity "
484+
head = (f"**{containment_pct:.0%} containment** + structural similarity "
424485
"(Levenshtein + bigram cosine both above threshold vs this PR's code shape)")
425486
else:
426-
head = f"**{containment_pct:.0f}% containment** in the earlier #{original}"
487+
head = f"**{containment_pct:.0%} containment** in the earlier #{original}"
427488
if will_block:
428489
tail = (f"\n\nThis is the **{MAX_WARNINGS}rd** copycat-like submission — the account is now "
429490
"**blocked** and the PR closed.")
@@ -495,6 +556,7 @@ def main():
495556
original = None; orig_author = None; best_containment = 0.0
496557
pr_level_containment = 0.0
497558
best_lev = 0.0; best_cos = 0.0; structural_fired = False
559+
main_cache = {}
498560

499561
for e_num in earlier_nums:
500562
e_author = pr_author.get(e_num, "")
@@ -520,16 +582,17 @@ def main():
520582

521583
# Layer 4: per-function containment (near-verbatim kernel inside larger PR).
522584
if c < COPYCAT_WARN and not structural_fired:
523-
func_c, func_csig, func_osig = per_function_containment(REPO, pr_num, e_num)
524-
if func_c >= FUNC_BLOCK_WARN:
585+
func_c, func_csig, func_osig, func_body, func_path = per_function_containment(
586+
REPO, pr_num, e_num, main_cache)
587+
if func_layer_should_warn(c, func_c, func_csig, func_body, func_path, REPO, main_cache):
525588
structural_fired = True; best_lev = func_c; best_cos = -1.0
526589
best_containment = max(best_containment, COPYCAT_WARN)
527590
original = e_num; orig_author = e_author
528591
print(f" per-function bump: {func_csig[:60]}... is {func_c:.0%} contained in #{e_num} -> WARN")
529-
elif _llm_enabled() and func_c >= LLM_FUNC_MIN:
592+
elif _llm_enabled() and func_c >= LLM_FUNC_MIN and not is_boilerplate_block(func_csig, func_body):
530593
print(f" layer 4 LLM: per-function containment={func_c:.1%} (vs #{e_num})")
531-
cb = next((b for s, b in split_into_blocks(REPO, pr_num) if s == func_csig), "")
532-
ob = next((b for s, b in split_into_blocks(REPO, e_num) if s == func_osig), "")
594+
cb = func_body
595+
ob = next((b for s, b, _p in split_into_blocks(REPO, e_num) if s == func_osig), "")
533596
is_copy, llm_conf, reason = llm_judge_copycat(cb, ob, func_csig, func_osig)
534597
print(f" LLM: copycat={is_copy} confidence={llm_conf:.2f} reason={reason[:120]}")
535598
if is_copy and llm_conf >= LLM_CONFIDENCE_MIN:

eval/copycat_policy.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
# Per-function dilution catch (near-verbatim kernel embedded in larger PR).
1313
# WARN only — never escalates to block on its own (PR-level containment must be ≥85%).
1414
FUNC_BLOCK_WARN = 0.92
15+
# Ignore tiny device helpers / shared dequant that already live on main (PR #566 FP:
16+
# pfm_scale_min_k4 matched 100% across MoE PRs but is identical on main).
17+
FUNC_MIN_BODY_TOKENS = 80
18+
# Skip func-layer warn when PR-level overlap is negligible — real dilution still
19+
# clears this when a whole kernel is pasted into a larger diff.
20+
FUNC_MIN_PR_LEVEL = 0.15
21+
# Line-set overlap vs origin/main for the same file → treat as shared infrastructure.
22+
FUNC_MAIN_SKIP = 0.90
1523

1624
# Structural (Levenshtein + bigram) layer disabled — too many FPs on independent
1725
# contributors converging on the same optimization pattern.

eval/copycat_sweep.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ def sweep(apply=False):
6565
detection = f"L2: {COPYCAT_WARN:.0%}{COPYCAT_BLOCK:.0%} containment"
6666

6767
if not detection:
68-
func_c, func_csig, func_osig = per_function_containment(REPO, pr_num, e_num)
69-
if func_c >= FUNC_BLOCK_WARN:
68+
func_c, func_csig, func_osig, func_body, func_path = per_function_containment(
69+
REPO, pr_num, e_num)
70+
if func_layer_should_warn(c, func_c, func_csig, func_body, func_path, REPO):
7071
detection = f"L4: per-function {func_c:.0%} (PR-level {c:.0%})"
7172
details = {"func_c": round(func_c, 3), "func_sig": func_csig[:80]}
72-
elif _llm_enabled() and func_c >= LLM_FUNC_MIN:
73-
cb = next((b for s, b in split_into_blocks(REPO, pr_num) if s == func_csig), "")
74-
ob = next((b for s, b in split_into_blocks(REPO, e_num) if s == func_osig), "")
73+
elif _llm_enabled() and func_c >= LLM_FUNC_MIN and not is_boilerplate_block(func_csig, func_body):
74+
cb = func_body
75+
ob = next((b for s, b, _p in split_into_blocks(REPO, e_num) if s == func_osig), "")
7576
is_copy, llm_c, reason = llm_judge_copycat(cb, ob, func_csig, func_osig)
7677
if is_copy and llm_c >= LLM_CONFIDENCE_MIN:
7778
detection = f"L4-LLM: confidence={llm_c:.0%}, func_c={func_c:.0%}"

eval/test_copycat_guard.py

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,105 @@
1-
"""Unit tests for copycat reference PR selection."""
1+
"""Unit tests for copycat reference PR selection + func-layer FP guards."""
2+
3+
import json
4+
import unittest
25

36
import copycat_guard as cg
47

58

6-
def test_list_reference_prs_filters_drafts(monkeypatch):
7-
payload = [
8-
{"number": 10, "author": {"login": "alice"}, "isDraft": False},
9-
{"number": 11, "author": {"login": "bob"}, "isDraft": True},
10-
{"number": 12, "author": {"login": "carol"}, "isDraft": False},
11-
]
9+
class CopycatGuardTests(unittest.TestCase):
10+
def test_list_reference_prs_filters_drafts(self):
11+
payload = [
12+
{"number": 10, "author": {"login": "alice"}, "isDraft": False},
13+
{"number": 11, "author": {"login": "bob"}, "isDraft": True},
14+
{"number": 12, "author": {"login": "carol"}, "isDraft": False},
15+
]
16+
17+
def fake_gh(args):
18+
class R:
19+
stdout = json.dumps(payload)
20+
returncode = 0
21+
return R()
22+
23+
old = cg.gh
24+
cg.gh = fake_gh
25+
try:
26+
out = cg.list_reference_prs("owner/repo", limit=50)
27+
self.assertEqual([p["number"] for p in out], [10, 12])
28+
finally:
29+
cg.gh = old
30+
31+
def test_list_reference_prs_uses_open_state(self):
32+
seen = {}
33+
34+
def fake_gh(args):
35+
seen["args"] = args
36+
class R:
37+
stdout = "[]"
38+
returncode = 0
39+
return R()
40+
41+
old = cg.gh
42+
cg.gh = fake_gh
43+
try:
44+
cg.list_reference_prs("owner/repo")
45+
self.assertIn("--state", seen["args"])
46+
self.assertEqual(seen["args"][seen["args"].index("--state") + 1], "open")
47+
finally:
48+
cg.gh = old
49+
50+
def test_tiny_device_helper_is_boilerplate(self):
51+
sig = "__device__ __forceinline__ void pfm_scale_min_k4(int j, const unsigned char* q, int* d, int* m) {"
52+
body = "\n".join([
53+
" if (j < 4) { *d = q[j] & 63; *m = q[j + 4] & 63; }",
54+
" else {",
55+
" *d = (q[j + 4] & 0xF) | ((q[j - 4] >> 6) << 4);",
56+
" *m = (q[j + 4] >> 4) | ((q[j] >> 6) << 4);",
57+
" }",
58+
])
59+
self.assertTrue(cg.is_boilerplate_block(sig, body))
1260

13-
def fake_gh(args):
14-
class R:
15-
stdout = __import__("json").dumps(payload)
16-
return R
61+
def test_large_kernel_not_boilerplate(self):
62+
sig = "__global__ void pfm_group_tilemap_kernel(const int* counts, int* out) {"
63+
body = "\n".join([f" int x{i} = counts[{i}] + out[{i}];" for i in range(40)])
64+
self.assertFalse(cg.is_boilerplate_block(sig, body))
1765

18-
monkeypatch.setattr(cg, "gh", fake_gh)
19-
out = cg.list_reference_prs("owner/repo", limit=50)
20-
assert [p["number"] for p in out] == [10, 12]
66+
def test_block_already_on_main(self):
67+
cache = {"kernels/csrc/cuda/fused/prefill_moe.cu": "\n".join([
68+
"__device__ void pfm_scale_min_k4(int j, const unsigned char* q, int* d, int* m) {",
69+
" if (j < 4) { *d = q[j] & 63; *m = q[j + 4] & 63; }",
70+
" else {",
71+
" *d = (q[j + 4] & 0xF) | ((q[j - 4] >> 6) << 4);",
72+
" *m = (q[j + 4] >> 4) | ((q[j] >> 6) << 4);",
73+
" }",
74+
"}",
75+
])}
76+
body = "\n".join([
77+
" if (j < 4) { *d = q[j] & 63; *m = q[j + 4] & 63; }",
78+
" else {",
79+
" *d = (q[j + 4] & 0xF) | ((q[j - 4] >> 6) << 4);",
80+
" *m = (q[j + 4] >> 4) | ((q[j] >> 6) << 4);",
81+
" }",
82+
])
83+
self.assertTrue(cg.block_already_on_main(
84+
"owner/repo", "kernels/csrc/cuda/fused/prefill_moe.cu", body, cache))
2185

86+
def test_func_layer_skips_pr566_style_fp(self):
87+
"""PR #566: 27% PR-level + 100% on tiny main helper must not warn."""
88+
sig = "__device__ __forceinline__ void pfm_scale_min_k4(int j, const unsigned char* q, int* d, int* m) {"
89+
body = "\n".join([
90+
" if (j < 4) { *d = q[j] & 63; *m = q[j + 4] & 63; }",
91+
" else {",
92+
" *d = (q[j + 4] & 0xF) | ((q[j - 4] >> 6) << 4);",
93+
" *m = (q[j + 4] >> 4) | ((q[j] >> 6) << 4);",
94+
" }",
95+
])
96+
self.assertFalse(cg.func_layer_should_warn(0.27, 1.0, sig, body))
2297

23-
def test_list_reference_prs_uses_open_state(monkeypatch):
24-
seen = {}
98+
def test_func_layer_warns_on_large_embedded_kernel(self):
99+
sig = "__global__ void stolen_moe_kernel(const int* a, int* b) {"
100+
body = "\n".join([f" int v{i} = a[{i}] * b[{i}] + {i}; __syncthreads();" for i in range(50)])
101+
self.assertTrue(cg.func_layer_should_warn(0.25, 0.95, sig, body))
25102

26-
def fake_gh(args):
27-
seen["args"] = args
28-
class R:
29-
stdout = "[]"
30-
return R
31103

32-
monkeypatch.setattr(cg, "gh", fake_gh)
33-
cg.list_reference_prs("owner/repo")
34-
assert "--state" in seen["args"]
35-
assert seen["args"][seen["args"].index("--state") + 1] == "open"
104+
if __name__ == "__main__":
105+
unittest.main()

0 commit comments

Comments
 (0)