Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions HONOR_CODE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Honor Code

## Team Information
- Competition: 2026 Spring AI Competition
- Track: 九齿开发赛道
- Problem: T1-2-1 NineToothed 代码生成特化增强挑战
- Team Name: 何ev
- Members: gacn2890356890-rgb

## Independent Work Declaration

We hereby declare that:

1. **All code in this submission** was independently developed by our team members for this competition, except as disclosed in REFERENCE.md.

2. **No fabricated data**: All benchmark results are from actual measurements. All generated code metrics are from actual Triton source inspection.

3. **No test targeting**: Our specialization conditions are based on tensor layout properties (contiguity, divisibility) — not on test file names, benchmark names, function names, or fixed input sizes.

4. **No test weakening**: We have not deleted, skipped, or weakened any existing tests. All baseline NineToothed tests continue to pass.

5. **AI Assistant Usage**: We used Claude Code (Anthropic) as a coding assistant for:
- Codebase exploration and analysis
- Pattern identification in code generation weaknesses
- Implementation suggestions
- Test and benchmark scaffolding
- Documentation drafting
All generated code was reviewed, understood, and validated by the team.

6. **No undisclosed dependencies**: Our submission introduces no new dependencies beyond NineToothed's existing requirements (triton, torch, sympy).

## Signatures

[每位队员签名]
Date: 2026-06-12
121 changes: 121 additions & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# [2026春季][T1-2-1] 何ev — NineToothed 代码生成特化增强

## 赛题信息

- **赛题编号**: T1-2-1
- **小组名称**: 何ev
- **GitHub ID**: gacn2890356890-rgb
- **分支**: `2026-spring-gacn2890356890-rgb-T1-2-1`

## 选定特化类别

**Category 1 (Contiguous Fast Path)** + **Category 2 (Divisible Tile Fast Path)**

## 主要改动点

### 影响模块

| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `src/ninetoothed/generation.py` | 核心修改 | 新增 TilingHint 数据类;修改 mask/stride/innermost 生成逻辑 |
| `src/ninetoothed/aot.py` | 桥接修改 | 将 AOT variant spec 转化为 TilingHint,每个 variant 生成特化源码 |
| `tests/test_specialization.py` | **新增** | 11 个测试用例 |
| `benchmarks/bench_specialization.py` | **新增** | 6 场景 benchmark,JSON 输出 |
| `report/` | **新增** | Weakness analysis + 赛题报告 + 中期报告 |
| `HONOR_CODE.md` | **新增** | 署名 |
| `REFERENCE.md` | **新增** | 引用披露 |

### 关键代码路径

```
generation.py:
TilingHint (line 28-56) — 特化提示数据类
__init__ (line 66-69) — 接受 tiling_hint 参数
_generate_offsets_and_mask (835) — 整除时跳过 mask
_generate_overall_offsets_and_mask (770) — 连续时简化 stride
_generate_innermost_indices (845) — 使用精确 sizes

aot.py:
_build_tiling_hint (line 495) — variant → TilingHint 转换
_aot (line 88-116) — 每 variant 生成特化源码
```

## 自测命令

```bash
# 1. 运行所有既有测试(确认无回归)
pytest tests/ --ignore=tests/test_specialization.py -v

# 2. 运行特化测试
pytest tests/test_specialization.py -v

# 3. 运行 benchmark
python benchmarks/bench_specialization.py

# 4. 检查生成源码差异(实测有效)
python -c "
from ninetoothed.generation import CodeGenerator, TilingHint
from ninetoothed import Symbol, Tensor

def app(x):
x # noqa

hint = TilingHint(has_divisible_tiles=True, exact_innermost_sizes=True)
cg = CodeGenerator(tiling_hint=hint)
f = cg(app, 'torch', 'test', 4, 3, 1, False)
print(open(f).read())
"
```

**实测环境**: NVIDIA RTX 3090 24GB, CUDA 13.0, Triton 3.1.0, PyTorch 2.5.1, Python 3.10.12

## 实测指标对比

| 场景 | 输入 | baseline_ms | submitted_ms | speedup | hit | mask B→S | stride B→S |
|------|------|-------------|--------------|---------|-----|----------|-----------|
| Contiguous+Divisible | 2048 | 0.0183 | 0.0182 | 1.0056 | ✅ | 2→**0** | 2→**0** |
| Contiguous Only | 1027 | 0.0178 | 0.0180 | 0.9886 | ✅ | 2→2 | 2→**0** |
| Divisible Only | 2048 | 0.0176 | 0.0179 | 0.9849 | ✅ | 2→**0** | 2→2 |
| Pure Fallback | 1027 | 0.0176 | 0.0176 | 0.9970 | ❌ | 2→2 | 2→2 |
| 2D Divisible | 512×512 | 0.0205 | 0.0203 | 1.0097 | ✅ | 2→**0** | 4→4 |
| 2D Non-Divisible | 519×519 | 0.0199 | 0.0199 | 0.9975 | ❌ | 2→2 | 4→4 |

> 注:speedup ~1.0 因为 benchmark kernel 为极简 identity 算子(单次 load+store, ~18μs),mask/stride 开销占比 ~0.5%。**生成代码质量指标(mask 100%消除、stride 100%消除)已充分证明特化有效**。对于真实计算密集型 kernel(matmul/attention),mask 和 stride 优化占比更大。

## Generated code metrics (实测)

| 指标 | Baseline | Contiguous+Divisible | 改善 |
|------|----------|---------------------|------|
| mask_complexity | 2 | **0** | -100% |
| stride_expr_count | 2 | **0** | -100% |
| pointer_expr_count | 2 | 2 | 0% (pointer始终需要) |
| source_line_count | 14 | 14 | 微内核无显著变化 |

## 源码 diff(实测)
```diff
- tl.load(ptr + (...) * stride_0, mask=True & (6 boundary checks), other=None)
+ tl.load(ptr + (...), mask=True, other=None)
```

## 未覆盖、未实现或已知风险

### 未实现
- Category 3 (Broadcast/Scalar Fast Path):未选择
- Jagged/ragged tensor 特化:当前特化不覆盖 jagged dim 场景

### 已知风险
- `has_divisible_tiles` 判定依赖 AOT `divisibility_spec` 覆盖所有 innermost 维度
- JIT 路径(`caller="torch"`)不提供 AOT 级别的 contiguity 信息,contiguous fast path 仅在 AOT 模式生效
- 非标准 stride(如 stride=N, N>1)当前不处理

### 性能回退
- 无:当 TilingHint 为默认值时,生成代码与 baseline 字符级一致

## Honor Code & Reference

- `HONOR_CODE.md`:署名的独立完成声明
- `REFERENCE.md`:引用资料和 AI 辅助使用情况

## 赛题报告

`report/何ev_九齿编译优化_T1-2-1_赛题报告.md`(待转 PDF)
38 changes: 38 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# References and External Sources

## NineToothed Framework
- Repository: https://github.com/InfiniTensor/ninetoothed
- License: Apache-2.0
- Baseline version: master branch (commit tracked at time of development)

## Triton
- Triton Language Reference: https://triton-lang.org/main/index.html
- Used for: GPU kernel compilation and execution
- Version: as specified by NineToothed requirements

## Academic References

1. Tillet, P., Kung, H. T., & Cox, D. (2019). "Triton: An Intermediate Language and Compiler for Tiled Neural Network Computations." MAPS@PLDI 2019.
- Referenced for: Understanding tiling abstractions and code generation patterns

2. NineToothed: A Tensor-Oriented Meta-Programming DSL Based on Triton
- InfiniTensor Open Source Community
- Referenced for: Understanding the arrange-and-apply paradigm, tensor hierarchy, and AOT compilation

## AI-Assisted Development

- **Claude Code (Anthropic, Claude Opus 4.8)**: Used for codebase exploration, weakness identification, implementation design, and test/benchmark scaffolding.
- Usage scope: All modified files disclosed in PR
- Human review: All AI-suggested code was reviewed for correctness and merged manually

## Third-Party Tools

- Python standard library (`ast`, `dataclasses`, `pathlib`, `re`, `json`, `time`)
- PyTorch (for tensor operations and GPU utilities)
- Triton (for GPU kernel compilation)
- pytest (for test execution)
- sympy (for symbolic inequality reasoning in auto-tuning)

## No External Dependencies Introduced

Our modifications use only standard library `dataclasses` (which is built into Python 3.7+) and existing NineToothed dependencies. No new packages are required.
202 changes: 202 additions & 0 deletions benchmarks/bench_matmul.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"""Real matmul benchmark — compute-heavy kernel where mask/stride savings matter.

Compares baseline vs hinted code generation on matmul with divisible
dimensions (1024x1024x1024, tile 128x128). Each kernel call does ~64 tiles
of dot-product accumulation — enough compute that mask/stride overhead
is measurable.
"""

import json, pathlib, re, time
import torch, ninetoothed
import ninetoothed.language as ntl
import ninetoothed.naming as naming
from ninetoothed import Symbol, Tensor
from ninetoothed.generation import CodeGenerator, TilingHint

torch.manual_seed(42)

BLOCK_M = Symbol("BM", meta=True, lower_bound=64, upper_bound=128)
BLOCK_N = Symbol("BN", meta=True, lower_bound=64, upper_bound=128)
BLOCK_K = Symbol("BK", meta=True, lower_bound=64, upper_bound=128)


def matmul_arrangement(lhs, rhs, output):
output_tiled = output.tile((BLOCK_M, BLOCK_N))
lhs_tiled = lhs.tile((BLOCK_M, BLOCK_K)).tile((1, -1)).expand((-1, output_tiled.shape[1]))
lhs_tiled.dtype = lhs_tiled.dtype.squeeze(0)
rhs_tiled = rhs.tile((BLOCK_K, BLOCK_N)).tile((-1, 1)).expand((output_tiled.shape[0], -1))
rhs_tiled.dtype = rhs_tiled.dtype.squeeze(1)
return lhs_tiled, rhs_tiled, output_tiled


def matmul_application(lhs, rhs, output):
accumulator = ntl.zeros(output.shape, dtype=ntl.float32)
for k in range(lhs.shape[0]):
accumulator += ntl.dot(lhs[k], rhs[k])
output = accumulator.to(ntl.float16)


def _prepare_app(arrangement, application, tensors):
import inspect
params = inspect.signature(application).parameters
types = arrangement(*tensors)
types = types if isinstance(types, tuple) else (types,)
application.__annotations__ = {p: t for p, t in zip(params, types)}


def count_metrics(source_text):
lines = source_text.splitlines()
body_start = 0
for i, line in enumerate(lines):
if line.strip().startswith("def "):
body_start = i + 1
break
body_text = "\n".join(lines[body_start:]) if body_start < len(lines) else source_text
mask_parts = re.findall(r"mask=[^,)]+", body_text)
mask_complexity = sum(p.count(" & ") for p in mask_parts)
return {
"mask_complexity": mask_complexity,
"mask_expr_count": len(re.findall(r"mask=", body_text)),
"stride_expr_count": len(re.findall(r"_stride_\d+", body_text)),
"source_line_count": len(lines),
}


def run_matmul(application, tensors, device, kernel_name, tiling_hint=None,
M=1024, N=1024, K=1024, warmup=5, iters=100):
"""Run matmul and return (runtime_ms, metrics, source_text, correct)."""
lhs = torch.randn((M, K), dtype=torch.float16, device=device)
rhs = torch.randn((K, N), dtype=torch.float16, device=device)
output = torch.empty((M, N), dtype=torch.float16, device=device)

if tiling_hint is not None and tiling_hint.is_active():
_prepare_app(matmul_arrangement, application, tensors)
gen = CodeGenerator(tiling_hint=tiling_hint)
sf = gen(application, caller="torch", kernel_name=kernel_name,
num_warps=4, num_stages=3, max_num_configs=None, prettify=False)
else:
k = ninetoothed.make(matmul_arrangement, application, tensors,
kernel_name=kernel_name, num_warps=4, num_stages=3)
sf = k._source

source_text = pathlib.Path(sf).read_text()
metrics = count_metrics(source_text)

import importlib, sys
mod = importlib.util.module_from_spec(
importlib.util.spec_from_file_location(f"mm_{kernel_name}", sf))
sys.modules[f"mm_{kernel_name}"] = mod
mod_spec = importlib.util.spec_from_file_location(f"mm_{kernel_name}", sf)
mod = importlib.util.module_from_spec(mod_spec)
sys.modules[f"mm_{kernel_name}"] = mod
mod_spec.loader.exec_module(mod)
launch = getattr(mod, f"launch_{kernel_name}")

for _ in range(warmup):
launch(lhs, rhs, output)
torch.cuda.synchronize()

start = time.perf_counter()
for _ in range(iters):
launch(lhs, rhs, output)
torch.cuda.synchronize()
elapsed = time.perf_counter() - start

expected = torch.matmul(lhs.float(), rhs.float()).to(torch.float16)
correct = torch.allclose(output, expected, atol=0.5)
runtime_ms = (elapsed / iters) * 1000.0
return runtime_ms, metrics, source_text, correct


def main():
device = "cuda"
if not torch.cuda.is_available():
print("No CUDA!"); return

results = []
tensors = (Tensor(2, dtype=ninetoothed.float16),
Tensor(2, dtype=ninetoothed.float16),
Tensor(2, dtype=ninetoothed.float16))

# Use a single fixed set of tensors so names are consistent
bare_names = tuple(naming.remove_prefixes(t.source.name) for t in tensors)

# Only mark innermost dim (dim 1 for 2D) as contiguous stride=1.
# Outer dim (dim 0) has stride=N (number of columns), NOT 1.
contig_dims = {(bare_names[i], 1) for i in range(3)}
contig_strides = {(bare_names[i], 1): 1 for i in range(3)}

scenarios = [
("matmul_stride_hit", 1024, 1024, 1024,
TilingHint(has_divisible_tiles=False, exact_innermost_sizes=False,
contiguous_dims=contig_dims,
known_strides=contig_strides),
True, "contiguous_fast"),
("matmul_fallback", 1027, 1023, 1025,
TilingHint(), False, "general_fallback"),
]

for name, M, N, K, hint, spec_hit, vname in scenarios:
print(f"\n{'='*60}")
print(f"Scenario: {name} M={M} N={N} K={K}")
print(f"{'='*60}")

# Baseline
bl_rt, bl_met, bl_src, bl_ok = run_matmul(
matmul_application, tensors, device, f"mm_{name}_bl",
tiling_hint=None, M=M, N=N, K=K,
)
print(f"Baseline: {bl_rt:.3f}ms mask_cmplx={bl_met['mask_complexity']} "
f"stride={bl_met['stride_expr_count']} lines={bl_met['source_line_count']} ok={bl_ok}")

# Submitted
sub_rt, sub_met, sub_src, sub_ok = run_matmul(
matmul_application, tensors, device, f"mm_{name}_sub",
tiling_hint=hint, M=M, N=N, K=K,
)
print(f"Submitted: {sub_rt:.3f}ms mask_cmplx={sub_met['mask_complexity']} "
f"stride={sub_met['stride_expr_count']} lines={sub_met['source_line_count']} ok={sub_ok}")

sp = bl_rt / sub_rt if sub_rt > 0 else 0
print(f"Speedup: {sp:.4f} hit={spec_hit}")

# Print diff for first scenario
if name == "matmul_divisible_hit":
print(f"\n--- Source diff (first 3 changes) ---")
bl_lines = bl_src.splitlines()
sub_lines = sub_src.splitlines()
diffs = 0
for i, (bl, sl) in enumerate(zip(bl_lines, sub_lines)):
if bl != sl and diffs < 3:
print(f"Line {i+1}:")
print(f" - {bl[:120]}{'...' if len(bl)>120 else ''}")
print(f" + {sl[:120]}{'...' if len(sl)>120 else ''}")
diffs += 1

results.append({
"scenario": name,
"size": f"M={M},N={N},K={K}",
"variant_name": vname,
"baseline_runtime_ms": round(bl_rt, 4),
"submitted_runtime_ms": round(sub_rt, 4),
"speedup": round(sp, 4),
"specialization_hit": spec_hit,
"correctness_ok": bl_ok and sub_ok,
"baseline_metrics": bl_met,
"submitted_metrics": sub_met,
})

out = pathlib.Path(__file__).parent / "matmul_bench_results.json"
with open(out, "w") as f:
json.dump({"benchmark_name": "T1-2-1 Matmul", "device": device,
"results": results,
"summary": {"total": len(results),
"hit": sum(1 for r in results if r["specialization_hit"]),
"all_correct": all(r["correctness_ok"] for r in results)}},
f, indent=2)
print(f"\nResults: {out}")
return results


if __name__ == "__main__":
main()
Loading