GPUCodeForces/scripts/update_operator_checklist.py

294 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Generate the implemented-operator checklist in README.md.
The checklist is driven by two sources:
- scripts/operator_targets.txt: known operators to track, one per line.
- S1 codes/: submitted implementations containing *cuda*.py files.
New implementations are marked as checked automatically when the discovered
operator name matches a target name. Operators discovered from submissions but
missing from the target file are included as checked rows as well.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
from urllib.parse import quote
ROOT = Path(__file__).resolve().parents[1]
README = ROOT / "README.md"
S1_DIR = ROOT / "S1 codes"
TARGETS = ROOT / "scripts" / "operator_targets.txt"
START = "<!-- OPERATOR_CHECKLIST_START -->"
END = "<!-- OPERATOR_CHECKLIST_END -->"
def clean_operator(value: str | None) -> str | None:
if not value:
return None
text = value.strip()
text = re.sub(r"\s*\((?:Fused\s+)?CUDA\s+Kernel\)\s*$", "", text)
text = re.sub(r"\s+with custom CUDA kernel\s*$", "", text)
text = re.sub(r"(一次核内.*$", "", text)
text = re.sub(r".*$", "", text)
text = re.sub(r"。.*$", "", text)
text = text.strip(" :.。")
return text or None
def normalize_key(value: str) -> str:
return re.sub(r"\s+", "", value).casefold()
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="ignore")
def operator_from_torch(torch_path: Path) -> str | None:
if not torch_path.exists():
return None
text = read_text(torch_path)
if re.search(r"torch\.relu\(x \* self\.scale \+ self\.bias\)", text):
return "Affine+ReLU"
if re.search(
r"torch\.maximum\(x,\s*torch\.zeros_like\(x\)\).*torch\.log1p\(torch\.exp\(-ax\)\)",
text,
re.S,
):
return "BCEWithLogitsLoss"
match = re.search(r"return\s+F\.([A-Za-z0-9_]+)\(", text)
if match:
return match.group(1)
match = re.search(r"return\s+torch\.([A-Za-z0-9_]+)\(", text)
if match:
return match.group(1)
return None
def operator_from_prompt(prompt_path: Path) -> str | None:
if not prompt_path.exists():
return None
skip_prefixes = (
"You write custom CUDA kernels",
"你需要为下面给定的架构",
"你可以自由选择",
"给定架构",
"torchcode.py",
"cudacode.py",
"run_code.py",
)
for raw_line in prompt_path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = re.sub(r"^\s*[#>*\-`]+\s*", "", raw_line).strip()
if not line:
continue
if line.startswith(skip_prefixes):
continue
match = re.match(r"^Operator\s*[:]\s*(.+)$", line)
if match:
return clean_operator(match.group(1))
match = re.match(r"^Objective\s*[:].*?\bfor\s+(.+?)\s+to\s+achieve", line)
if match:
return clean_operator(match.group(1))
match = re.match(r"^Implement\s+(?:a\s+|an\s+)?(.+?)(?:\s+on\b|\s+for\b|\s*[:]|$)", line)
if match:
return clean_operator(match.group(1))
match = re.search(r"“([^”]+)”", line)
if match:
return clean_operator(match.group(1))
match = re.search(r"算子\s*[:]\s*([^。;;,]+)", line)
if match:
return clean_operator(match.group(1))
match = re.match(r"^(.+?)融合\s*[:]", line)
if match:
return clean_operator(match.group(1) + "融合")
if len(line) <= 80:
return clean_operator(line)
return None
def discover_operator(directory: Path) -> str | None:
cuda_files = sorted(directory.glob("*cuda*.py"), key=lambda p: p.name.casefold())
if not cuda_files:
return None
cuda_base = cuda_files[0].stem
if re.match(r"^(example_)?cudacode$", cuda_base, re.I):
torch_files = sorted(directory.glob("*torch*.py"), key=lambda p: p.name.casefold())
specific_torch = [
path for path in torch_files if not re.match(r"^(example_)?torchcode$", path.stem, re.I)
]
if specific_torch:
return re.sub(r"_?torch(code)?$", "", specific_torch[0].stem, flags=re.I).strip()
return (
operator_from_prompt(directory / "prompt.txt")
or operator_from_torch(directory / "torchcode.py")
or cuda_base
)
return re.sub(r"_?cudacode$|_?cuda$", "", cuda_base, flags=re.I).strip()
def discover_implemented() -> dict[str, dict[str, object]]:
implemented: dict[str, dict[str, object]] = {}
if not S1_DIR.exists():
return implemented
for directory in sorted((path for path in S1_DIR.iterdir() if path.is_dir()), key=lambda p: p.name.casefold()):
operator = clean_operator(discover_operator(directory))
if not operator:
continue
key = normalize_key(operator)
row = implemented.setdefault(key, {"name": operator, "directories": []})
directories = row["directories"]
assert isinstance(directories, list)
directories.append(directory.name)
return implemented
def read_targets() -> list[str]:
if not TARGETS.exists():
return []
targets: list[str] = []
seen: set[str] = set()
for raw_line in TARGETS.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
key = normalize_key(line)
if key in seen:
continue
seen.add(key)
targets.append(line)
return targets
def write_targets(targets: list[str]) -> None:
body = [
"# Operator targets tracked in README.md",
"# Add unimplemented operators here, one per line.",
"# scripts/update_operator_checklist.py checks them when matching implementations appear under S1 codes/.",
"",
*targets,
"",
]
TARGETS.write_text("\n".join(body), encoding="utf-8", newline="\n")
def link_for_directory(directory: str) -> str:
return f"[{directory}](S1%20codes/{quote(directory)})"
def build_section(targets: list[str], implemented: dict[str, dict[str, object]]) -> str:
target_by_key = {normalize_key(target): target for target in targets}
all_keys = set(target_by_key) | set(implemented)
checked = len([key for key in all_keys if key in implemented])
unchecked = len(all_keys) - checked
rows: list[tuple[bool, str, list[str]]] = []
for key in sorted(all_keys, key=lambda item: (item not in implemented, (target_by_key.get(item) or implemented[item]["name"]).casefold())):
item = implemented.get(key)
name = target_by_key.get(key) or str(item["name"])
directories = list(item["directories"]) if item else []
rows.append((item is not None, name, directories))
lines = [
START,
"## ✅ 算子实现状态",
"",
"该清单由 `scripts/update_operator_checklist.py` 根据 `scripts/operator_targets.txt` 和 `S1 codes/` 自动生成;新增待实现算子请写入目标清单,新增实现目录后运行 `python scripts/update_operator_checklist.py --sync-targets` 即可自动勾选。",
"",
f"- 已实现:{checked}",
f"- 未实现:{unchecked}",
f"- 跟踪总数:{len(all_keys)}",
"",
"<details>",
"<summary>展开查看算子实现状态</summary>",
"",
"| 状态 | 算子 | 实现目录 |",
"| --- | --- | --- |",
]
for done, name, directories in rows:
status = "[x]" if done else "[ ]"
links = "<br>".join(link_for_directory(directory) for directory in directories) if directories else "-"
lines.append(f"| {status} | {name.replace('|', r'\|')} | {links} |")
lines.extend(["", "</details>", END])
return "\n".join(lines)
def update_readme(section: str) -> None:
content = README.read_text(encoding="utf-8")
old_static_pattern = r"(?s)\n?<!-- IMPLEMENTED_OPERATORS_START -->.*?<!-- IMPLEMENTED_OPERATORS_END -->\n?"
content = re.sub(old_static_pattern, "\n", content)
pattern = rf"(?s){re.escape(START)}.*?{re.escape(END)}"
if re.search(pattern, content):
updated = re.sub(pattern, section, content)
else:
heading = re.compile(r"(?m)^## 📥")
if heading.search(content):
updated = heading.sub(section + "\n\n---\n\n## 📥", content, count=1)
else:
updated = content.rstrip() + "\n\n" + section + "\n"
updated = re.sub(r"(?m)(?:^---\n\s*){2,}(?=<!-- OPERATOR_CHECKLIST_START -->)", "---\n\n", updated)
README.write_text(updated.rstrip() + "\n", encoding="utf-8", newline="\n")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--sync-targets",
action="store_true",
help="Append discovered implemented operators to scripts/operator_targets.txt.",
)
args = parser.parse_args()
implemented = discover_implemented()
targets = read_targets()
if args.sync_targets:
by_key = {normalize_key(target): target for target in targets}
for key, item in implemented.items():
by_key.setdefault(key, str(item["name"]))
targets = sorted(by_key.values(), key=str.casefold)
write_targets(targets)
section = build_section(targets, implemented)
update_readme(section)
print(
f"Updated README operator checklist: "
f"{len(implemented)} implemented operators, {len(targets)} tracked targets."
)
return 0
if __name__ == "__main__":
raise SystemExit(main())