Implement Static Profiler inside post.py and add termspec comparison pipeline
This commit is contained in:
parent
13a6ef823d
commit
71c53c1b09
2 changed files with 223 additions and 0 deletions
|
|
@ -193,9 +193,62 @@ class SympyOptimizer:
|
|||
self.sympy_cache[name] = expanded_expr
|
||||
return expanded_expr
|
||||
|
||||
def calculate_flops_and_heavy(self, expr):
|
||||
flops = 0
|
||||
heavy = 0
|
||||
for node in sympy.preorder_traversal(expr):
|
||||
if isinstance(node, sympy.Add):
|
||||
flops += len(node.args) - 1
|
||||
elif isinstance(node, sympy.Mul):
|
||||
flops += len(node.args) - 1
|
||||
elif isinstance(node, sympy.Pow):
|
||||
base, exp = node.args
|
||||
if exp == 0.5 or exp == -0.5:
|
||||
flops += 10
|
||||
heavy += 1
|
||||
elif exp == -1:
|
||||
flops += 4
|
||||
heavy += 1
|
||||
elif isinstance(exp, sympy.Integer):
|
||||
val = abs(int(exp))
|
||||
if val > 1:
|
||||
flops += val - 1
|
||||
else:
|
||||
flops += 10
|
||||
heavy += 1
|
||||
elif isinstance(node, (sympy.Derivative, sympy.Function)):
|
||||
name = node.func.__name__
|
||||
if name == 'sqrt':
|
||||
flops += 10
|
||||
heavy += 1
|
||||
elif name in ('exp', 'log', 'sin', 'cos', 'tan', 'rxn_rate'):
|
||||
flops += 10
|
||||
heavy += 1
|
||||
elif name == 'Abs':
|
||||
flops += 1
|
||||
else:
|
||||
flops += 10
|
||||
heavy += 1
|
||||
return flops, heavy
|
||||
|
||||
def count_3d_loads(self, expr, three_d_arrays):
|
||||
count = 0
|
||||
for node in sympy.preorder_traversal(expr):
|
||||
if isinstance(node, sympy.Symbol) and node.name in three_d_arrays:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def optimize_field(self, name, alloc=None):
|
||||
expr = self.get_sympy_expr(name)
|
||||
|
||||
three_d_arrays = {
|
||||
k for k, v in self.fdict.items()
|
||||
if hasattr(v, 'dim') and v.dim == ':,:,:'
|
||||
}
|
||||
|
||||
before_flops, before_heavy = self.calculate_flops_and_heavy(expr)
|
||||
before_loads = self.count_3d_loads(expr, three_d_arrays)
|
||||
|
||||
simplified_expr = sympy.simplify(expr)
|
||||
simplified_expr = sympy.cancel(simplified_expr)
|
||||
|
||||
|
|
@ -213,6 +266,47 @@ class SympyOptimizer:
|
|||
replacements, reduced_exprs = sympy.cse(simplified_expr)
|
||||
reduced_expr = reduced_exprs[0]
|
||||
|
||||
after_flops = 0
|
||||
after_heavy = 0
|
||||
after_loads = 0
|
||||
|
||||
for temp_var, temp_expr in replacements:
|
||||
f_val, h_val = self.calculate_flops_and_heavy(temp_expr)
|
||||
after_flops += f_val
|
||||
after_heavy += h_val
|
||||
after_loads += self.count_3d_loads(temp_expr, three_d_arrays)
|
||||
|
||||
f_val, h_val = self.calculate_flops_and_heavy(reduced_expr)
|
||||
after_flops += f_val
|
||||
after_heavy += h_val
|
||||
after_loads += self.count_3d_loads(reduced_expr, three_d_arrays)
|
||||
|
||||
import sys
|
||||
|
||||
def pct_str(before, after):
|
||||
if before == 0:
|
||||
return "0.0%" if after == 0 else "+inf%"
|
||||
diff = after - before
|
||||
pct = (diff / before) * 100
|
||||
return f"{pct:+.1f}%"
|
||||
|
||||
flops_pct = pct_str(before_flops, after_flops)
|
||||
heavy_pct = pct_str(before_heavy, after_heavy)
|
||||
loads_pct = pct_str(before_loads, after_loads)
|
||||
|
||||
if after_flops < before_flops * 0.5 or after_loads < before_loads * 0.5:
|
||||
est_speedup = "Highly significant"
|
||||
elif after_flops < before_flops or after_loads < before_loads:
|
||||
est_speedup = "Moderate"
|
||||
else:
|
||||
est_speedup = "Minimal / Already optimal"
|
||||
|
||||
sys.stderr.write(f"\n[SymPy Optimizer Report: {name}]\n")
|
||||
sys.stderr.write(f"- Floating Point Ops : {before_flops} -> {after_flops} ({flops_pct})\n")
|
||||
sys.stderr.write(f"- Heavy Ops (Div/Sqrt): {before_heavy} -> {after_heavy} ({heavy_pct})\n")
|
||||
sys.stderr.write(f"- 3D Array Mem Reads : {before_loads} -> {after_loads} ({loads_pct})\n")
|
||||
sys.stderr.write(f"=> Estimated Speedup in loop: {est_speedup}\n\n")
|
||||
|
||||
cse_decls = []
|
||||
cse_assigns = []
|
||||
|
||||
|
|
|
|||
129
scratch/compare_termspecs.py
Normal file
129
scratch/compare_termspecs.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import os
|
||||
import subprocess
|
||||
import difflib
|
||||
import re
|
||||
|
||||
termspecs = [
|
||||
"termspec.20210708",
|
||||
"termspec.banded_fsd-0.05-0.95-0.1",
|
||||
"termspec.fsd-pos-neg",
|
||||
"termspec.masked_fsd-0.1-0.9",
|
||||
"termspec.pdf-sampling-ic1",
|
||||
"termspec.pdf-sampling-ic1-ddxc",
|
||||
"termspec.t1-8",
|
||||
"termspec.vnsd_fluctuation"
|
||||
]
|
||||
|
||||
workspace_dir = "/home/ignis/workspace/incomp-flame-post"
|
||||
termspecs_dir = os.path.join(workspace_dir, "code/termspecs")
|
||||
artifacts_dir = "/home/ignis/.gemini/antigravity-ide/brain/738c7ef6-4504-4268-bb13-023dffb2a2eb"
|
||||
report_path = os.path.join(artifacts_dir, "sympy_comparison_report.md")
|
||||
|
||||
def run_cmd(args):
|
||||
res = subprocess.run(args, capture_output=True, text=True, cwd=workspace_dir)
|
||||
return res.stdout, res.stderr, res.returncode
|
||||
|
||||
def extract_loops_diff(base_code, opt_code):
|
||||
"""
|
||||
Diff the two source codes and extract the main loop calculation hunks.
|
||||
"""
|
||||
base_lines = base_code.splitlines(keepends=True)
|
||||
opt_lines = opt_code.splitlines(keepends=True)
|
||||
|
||||
diff = list(difflib.unified_diff(base_lines, opt_lines, fromfile='baseline', tofile='optimized', n=4))
|
||||
|
||||
# Filter hunks to keep the report clean and concise
|
||||
hunks = []
|
||||
current_hunk = []
|
||||
in_hunk = False
|
||||
|
||||
for line in diff:
|
||||
if line.startswith('@@'):
|
||||
if current_hunk:
|
||||
hunks.append("".join(current_hunk))
|
||||
current_hunk = []
|
||||
in_hunk = True
|
||||
current_hunk.append(line)
|
||||
elif in_hunk:
|
||||
current_hunk.append(line)
|
||||
|
||||
if current_hunk:
|
||||
hunks.append("".join(current_hunk))
|
||||
|
||||
# Standardize or filter non-essential changes (like comments with date or file paths)
|
||||
meaningful_hunks = []
|
||||
for hunk in hunks:
|
||||
lines = hunk.splitlines()
|
||||
has_logic_change = False
|
||||
for l in lines:
|
||||
if (l.startswith('+') or l.startswith('-')) and not l.startswith('+++') and not l.startswith('---'):
|
||||
# Ignore trivial comments or compilation timestamp lines if any
|
||||
if "build date" in l or "build base" in l or "build_date" in l:
|
||||
continue
|
||||
has_logic_change = True
|
||||
break
|
||||
if has_logic_change:
|
||||
meaningful_hunks.append(hunk)
|
||||
|
||||
return "\n".join(meaningful_hunks[:4]) # limit to first 4 meaningful hunks to keep it extremely readable
|
||||
|
||||
report_content = []
|
||||
report_content.append("# SymPy 기호 최적화 도입 전후 생성 코드 정밀 비교 보고서\n")
|
||||
report_content.append("본 보고서는 8가지 모든 termspec 설정에 대해 SymPy 대수 단순화 및 루프 단위 공통 하위식 제거(CSE) 최적화 도입 전(Baseline)과 도입 후(Optimized) 생성되는 포트란 코드를 대조 분석하여 정량적 최적화 메트릭 및 코드 수준의 차이점을 시각적으로 증명합니다.\n")
|
||||
|
||||
report_content.append("````carousel")
|
||||
|
||||
for idx, ts in enumerate(termspecs):
|
||||
ts_path = os.path.join(termspecs_dir, ts)
|
||||
print(f"Processing termspec: {ts}")
|
||||
|
||||
# Generate baseline
|
||||
base_out, base_err, base_code = run_cmd([
|
||||
"python3", "code/code_gen/code_gen_baseline.py", ts_path
|
||||
])
|
||||
|
||||
# Generate optimized (profile report prints to stderr)
|
||||
opt_out, opt_err, opt_code = run_cmd([
|
||||
"python3", "code/code_gen/code_gen.py", ts_path
|
||||
])
|
||||
|
||||
# Parse profile report from opt_err
|
||||
reports = []
|
||||
# Profile reports are bounded by [SymPy Optimizer Report: ...] and Estimated Speedup
|
||||
matches = re.findall(r"(\[SymPy Optimizer Report: [a-zA-Z0-9_]+\]\n.*?\n=> Estimated Speedup in loop:.*?\n)", opt_err, re.DOTALL)
|
||||
|
||||
# Create diff
|
||||
loop_diff = extract_loops_diff(base_out, opt_out)
|
||||
|
||||
report_content.append(f"## {idx+1}. {ts}\n")
|
||||
report_content.append("### 📊 SymPy 정적 분석 최적화 메트릭 요약\n")
|
||||
|
||||
if matches:
|
||||
report_content.append("```text")
|
||||
for r in matches[:5]: # show up to 5 field reports for conciseness
|
||||
report_content.append(r.strip())
|
||||
if len(matches) > 5:
|
||||
report_content.append(f"... (외 {len(matches)-5}개 필드는 생략됨)")
|
||||
report_content.append("```\n")
|
||||
else:
|
||||
report_content.append("> [!NOTE]\n> 본 termspec은 단순 변수나 primary field 위주로 구성되어 별도의 복잡한 기호 최적화 타겟이 발견되지 않았습니다.\n")
|
||||
|
||||
report_content.append("### 💻 생성 코드 차이점 상세 (Diff)\n")
|
||||
if loop_diff:
|
||||
report_content.append("```diff")
|
||||
report_content.append(loop_diff)
|
||||
report_content.append("```\n")
|
||||
else:
|
||||
report_content.append("> [!TIP]\n> 생성된 포트란 코드 로직이 완전히 완벽하게 동일하여 물리 수치 및 연산량이 기존과 100% 일치합니다. (최적화 오버헤드 0%)\n")
|
||||
|
||||
if idx < len(termspecs) - 1:
|
||||
report_content.append("<!-- slide -->")
|
||||
|
||||
report_content.append("````\n")
|
||||
|
||||
# Write report
|
||||
os.makedirs(os.path.dirname(report_path), exist_ok=True)
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(report_content))
|
||||
|
||||
print(f"SymPy termspec comparison report built at: {report_path}")
|
||||
Loading…
Add table
Reference in a new issue