Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
4e2ebd3
[Frontend] Follow tnpu's checkout rename in triton_src_dir
student-Jungmin Aug 6, 2026
eb352de
[Frontend] Bind libdevice instead of refusing the kernel
student-Jungmin Aug 6, 2026
e6afeff
[Frontend] Set estimate_peak, and vendor the pure-triton helpers
student-Jungmin Aug 6, 2026
bff0dd6
[Docs] Make shipping automatic, and say which remote
student-Jungmin Aug 6, 2026
751cf09
[Frontend] Fix the multi-axis grid, and size blocks from hardware_config
student-Jungmin Aug 6, 2026
f737d2e
[Frontend] Carry the dtypes tnpu already round-trips, any, and R0_BLOCK
student-Jungmin Aug 6, 2026
c90f017
[Frontend] Vendor the welford helpers too
student-Jungmin Aug 6, 2026
cbf9717
[Frontend] Four blockers between DeepSeek-V3 and the Triton route
student-Jungmin Aug 9, 2026
7858a17
[Frontend] Carry the mm/conv template wiring onto this branch, and re…
student-Jungmin Aug 9, 2026
ad09f9d
[Frontend] Expand integer min/max before the EmitC pipeline
student-Jungmin Aug 9, 2026
06951dc
[Frontend] Size conv blocks from the lane count, not from the GPU table
student-Jungmin Aug 9, 2026
8d35a60
[Frontend] Take a template kernel's launch grid from the template
student-Jungmin Aug 9, 2026
1c1887c
[Frontend] Take a template kernel's grid from its own grid function
student-Jungmin Aug 9, 2026
9d78822
[Frontend] Marshal a tensor in storage order, not in logical order
student-Jungmin Aug 10, 2026
849af5f
[Frontend] Replay a Triton kernel's Spike run when nothing it depends…
student-Jungmin Aug 10, 2026
962fd55
[Frontend] Make the Spike replay cache opt-in
student-Jungmin Aug 10, 2026
57232a2
[CI] Gate resnet18 on the Triton codegen route
student-Jungmin Aug 10, 2026
621a51b
[Frontend] Launch a grouped conv over one group's channels, and let t…
student-Jungmin Aug 10, 2026
2d75c17
[Frontend] Switch the timing half off with the config the MLIR route …
student-Jungmin Aug 10, 2026
95ab0a9
[Frontend] Vendor the bitonic sort helpers, for top-k
student-Jungmin Aug 10, 2026
d7da05f
[CI] Gate the Triton route on values, not on cycles, and add mobilenet
student-Jungmin Aug 10, 2026
9459069
[Frontend] Call a reduction persistent when our own block already cov…
student-Jungmin Aug 10, 2026
667eedd
[Frontend] Stop splitting reductions while a split cannot be collected
student-Jungmin Aug 10, 2026
8749a8b
[CI] Gate test_transformer on the Triton codegen route
student-Jungmin Aug 11, 2026
62d4e12
[Scripts] Clear the Triton route's artifacts too, for the reason they…
student-Jungmin Aug 11, 2026
026cef5
[Frontend] Ask Inductor for persistent reductions, through its own ch…
student-Jungmin Aug 11, 2026
ab60f6d
[Frontend] Size a Triton-route buffer by its strides, not by its shape
student-Jungmin Aug 11, 2026
47f0ae9
[Frontend] Ask the tensor for its strides, not the copy of it; test_v…
student-Jungmin Aug 11, 2026
609e036
[Frontend] Stop claiming dim 0 is the lane axis, and that a lane hold…
student-Jungmin Aug 11, 2026
80c296b
[Frontend] Gate CLIP and single_perceptron; vendor the two integer he…
student-Jungmin Aug 11, 2026
715a55a
[Frontend] Record why SwinV2 is blocked, and that it is not blocked here
student-Jungmin Aug 11, 2026
a2d5c9c
[Frontend] Hold a launch lock so two sessions cannot swap each other'…
student-Jungmin Aug 11, 2026
7b9e256
[Scripts] Clear the Triton route's artifacts too, for the reason they…
student-Jungmin Aug 11, 2026
23e03ae
[Frontend] Ask Inductor for persistent reductions, through its own ch…
student-Jungmin Aug 11, 2026
199cfc4
[Frontend] Size a Triton-route buffer by its strides, not by its shape
student-Jungmin Aug 11, 2026
88cdbba
[Frontend] Ask the tensor for its strides, not the copy of it; test_v…
student-Jungmin Aug 11, 2026
1511635
[Frontend] Gate CLIP and single_perceptron; vendor the two integer he…
student-Jungmin Aug 11, 2026
95aa32e
Merge feature/triton-codegen into develop-npu
student-Jungmin Aug 11, 2026
0b6ae9d
[Frontend] Let the Triton route honour pytorchsim_timing_mode
student-Jungmin Aug 11, 2026
1dca868
[Frontend] Make the TOGSim YAML the one machine, and fix the template…
student-Jungmin Aug 11, 2026
6c7565e
Merge e2e-transformer/scratch into develop-npu
student-Jungmin Aug 11, 2026
cea96eb
[Frontend] Gate convnextv2, which a masked-gather fix opened
student-Jungmin Aug 11, 2026
aaa6fa0
[Frontend] Record that swinv2's only blocker is the upstream one
student-Jungmin Aug 12, 2026
35cba64
[Frontend] Build swinv2's mask on the device instead of compiling aro…
student-Jungmin Aug 12, 2026
b9f6d7e
[Tests] GPT-2 end to end on the Triton route
student-Jungmin Aug 12, 2026
e0eec1f
[Frontend] Gate yolov5, whose only blocker was a stray opencv build
student-Jungmin Aug 12, 2026
c17ef94
[Frontend] Read a transfer's indirect index off the types, not off sl…
student-Jungmin Aug 12, 2026
75feb81
[Frontend] Count a template kernel's grid axes once, and guard the count
student-Jungmin Aug 12, 2026
45bb857
[Frontend] Take mm tiles from PyTorchSim's own mapping, and drop the …
student-Jungmin Aug 12, 2026
259533b
[Frontend] Force timing mode off on this branch, and fix what that ex…
student-Jungmin Aug 12, 2026
e93d804
[Tests] Run BERT end to end on the Triton codegen route
claude Aug 12, 2026
97e6393
[Frontend] Clamp the mm loads instead of folding them, on both sides now
student-Jungmin Aug 12, 2026
4381993
[Frontend] Verify a buffer after the LAST kernel that writes it
student-Jungmin Aug 12, 2026
f5ab638
[Frontend] Name what the lane-count cap is actually holding up
student-Jungmin Aug 12, 2026
fabb8e9
[Tests] Gate GPT-2 on the Triton route at the preset that passes
student-Jungmin Aug 12, 2026
de80b01
[Frontend] Say what the lane-count cap is over, now that two of three…
student-Jungmin Aug 12, 2026
0e1e94c
[Frontend] Reach the bmm spelling of the wrap, and drop the lane-coun…
student-Jungmin Aug 12, 2026
bc4cb0e
[Frontend] Gate DeepSeek-V3, which now passes through the Triton route
student-Jungmin Aug 12, 2026
a7d5058
[Frontend] Ask the tile mapping about a rounded shape, so nothing is …
student-Jungmin Aug 12, 2026
d36d0b1
[Frontend] Measure the scratchpad reservation instead of calling it a…
student-Jungmin Aug 12, 2026
ceb986c
[Frontend] Gate Mistral, and give the test the name it already answer…
student-Jungmin Aug 12, 2026
d615201
[Frontend] Never hand down a block wider than its numel, and sum(dim=…
student-Jungmin Aug 12, 2026
646f066
Merge remote-tracking branch 'fork/develop-npu' into develop-select-grid
student-Jungmin Aug 12, 2026
42b4645
[Repo] Ignore .envrc, which docs/worktrees.md already calls uncommitted
student-Jungmin Aug 12, 2026
7f67729
[Tests] Raise the GPT-2 gate to the real 768-wide block
student-Jungmin Aug 12, 2026
cfdf9eb
[Frontend] Persist a reduction only at a block this backend would hav…
claude Aug 12, 2026
74ded09
[Frontend] Do not clamp a block whose numel is 1, which broke Mistral…
student-Jungmin Aug 12, 2026
c51f682
Merge remote-tracking branch 'fork/develop-npu' into e2e-gpt2/scratch
student-Jungmin Aug 12, 2026
554b6cc
[Repo] Untrack .envrc, which the merge used to replace this worktree'…
student-Jungmin Aug 12, 2026
f2fcf79
Merge develop-npu: the block-sizing and lane-cap work under this change
student-Jungmin Aug 12, 2026
c36616c
Merge remote-tracking branch 'fork/develop-npu' into e2e-gpt2/scratch
student-Jungmin Aug 12, 2026
99c698b
Merge remote-tracking branch 'fork/e2e-bert/scratch' into e2e-gpt2/sc…
student-Jungmin Aug 12, 2026
76b8e9d
[Tests] Gate BERT on the real 768-wide block, and read the spad size …
claude Aug 12, 2026
fb10765
Merge e2e-bert/scratch: BERT end to end, and persistence declined whe…
student-Jungmin Aug 12, 2026
9825b6e
[Tests] Record that all four GPT-2 presets pass, up to GPT-2 base itself
student-Jungmin Aug 12, 2026
6ef4342
Merge remote-tracking branch 'fork/develop-npu' into e2e-gpt2/scratch
student-Jungmin Aug 12, 2026
17dcde9
[Frontend] Verify a buffer after a kernel that WRITES it, not one tha…
student-Jungmin Aug 12, 2026
d9803b6
[Frontend] Add a Llama 3 test and gate both Llama tests on the Triton…
student-Jungmin Aug 12, 2026
815258b
[Frontend] Gate Stable Diffusion v1.5, which now runs end to end
student-Jungmin Aug 13, 2026
a4fb8a0
Merge branch 'develop-e2e-sd15' into HEAD
student-Jungmin Aug 13, 2026
43a719f
[Frontend] Stop tracking PyTorchSimDevice build output
student-Jungmin Aug 13, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/pytorchsim_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ jobs:
echo "Running test_mistral.py"
docker run --rm \
-e TOGSIM_CONFIG="${{ inputs.togsim_config }}" \
${{ inputs.image_name }} python3 PyTorchSim/tests/models/Mixtral8x7B/test_attention.py
${{ inputs.image_name }} python3 PyTorchSim/tests/models/Mixtral8x7B/test_mistral.py

test_vit:
name: Run test_vit
Expand Down
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ tutorial/session1/fused/
tutorial/session1/togsim_results/
tutorial/session2/togsim_results/
tutorial/session2/fx_graph.svg

# Per-worktree environment, documented as local and uncommitted in
# docs/worktrees.md -- it names absolute paths that differ per worktree.
.envrc

# PyTorchSimDevice build output. CMake's tree and the extension modules it
# links -- 105 of these were committed by accident in 870a00e and are removed
# in the commit that adds these lines. They are regenerated by
# cd PyTorchSimDevice && python -m pip install --no-build-isolation -e .
PyTorchSimDevice/build/
PyTorchSimDevice/torch_openreg/lib/
PyTorchSimDevice/torch_openreg/_C.cpython-*.so
46 changes: 46 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,49 @@ Conan deps for TOGSim: `boost/1.79.0`, `robin-hood-hashing/3.11.5`, `spdlog/1.11
- Fork, branch (`feature/<name>`), PR against `develop`, not `main`.
- Commit prefix style observed: `[Frontend] ...`, `[TOGSim] ...`, etc.
- Commit messages: plain text only. No Markdown formatting (no backticks, bold, bullet lists, headings). Avoid Unicode where ASCII works (use `->` not arrows, `--` not em-dashes, straight quotes).

## Ship it — commit and push without being asked

Work that changes behaviour is done when it is **pushed**, not when it runs. Commit
and push in the same turn that finishes it. Do not stop to ask permission for
either; this rule is the permission.

implement -> verify -> commit -> push

**WHERE IT GOES.** `origin` is `PSAL-POSTECH/PyTorchSim`, the upstream everyone
shares, and it is **not** our destination. Push to the fork:

```bash
git remote add fork git@github.com:student-Jungmin/PyTorchSim.git # once
git push fork <branch>
```

Reading "push" as "push to origin" puts work on the shared upstream, so check the
remote before pushing rather than trusting whatever `origin` happens to be. A
change spanning repositories is one commit per repository, each to its own remote
— never one commit carrying another repository's work. (`triton-npu` has the
matching rule and the full destination table.)

**VERIFY FIRST, AND SAY WHAT RAN.** "Verified" means the test was executed, not
that the code looks right. For the Triton route that means the affected test
plus the allowlist (`scripts/ci/triton_route_passing.txt`) — and **clear
`outputs/triton_*` and `outputs/.torchinductor` between runs**, or a cached
artifact replays and a fix appears to change nothing. That has already caused one
wrong conclusion. A push whose verification was skipped is a push of an unknown
state.

**PIN `TNPU_DIR` WHEN THE ROUTE IS INVOLVED.** Stages 1-5 live in a separate repo
that someone else may be editing right now, and a pass mid-refactor produces
failures that look like ours (`no lane axis`, bare `NameError`s that vanish on
re-run). Point `TNPU_DIR` at a worktree pinned to a known-good commit so a result
means something.

**WHAT DOES NOT COUNT.** Exploration, scratch files, a half-finished edit, or a
change whose verification failed. Broken work is not pushed — say what failed.

**IF THE PUSH FAILS,** report it and why. Until it lands the work is committed,
not shipped; never present the one as the other.

**A REBUCKETED FAILURE IS NOT A FIX.** Clearing a guard so a test fails deeper is
progress worth committing, but say so in those words rather than counting it as
passing.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def synchronize(self) -> str:
return "pass"

def device_guard(self, device_idx: int) -> str:
return "pass"
# The caller writes `with {this}:`, so "pass" is a SyntaxError.
return "torch._ops.contextlib.nullcontext()"

register_device_op_overrides("npu", ExtensionDeviceOpOverrides())
register_device_op_overrides("cpu", CpuDeviceOpOverrides())
17 changes: 16 additions & 1 deletion PyTorchSimFrontend/extension_config.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

없애주세요

Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,22 @@ def __getattr__(name):
if name == "pytorchsim_functional_mode":
return config_yaml['pytorchsim_functional_mode']
if name == "pytorchsim_timing_mode":
return config_yaml['pytorchsim_timing_mode']
# FORCED OFF ON THIS BRANCH, ON PURPOSE AND TEMPORARILY. Correctness is
# what is being worked on here and the timing half costs a gem5 sample
# and a TOGSim run per kernel, so every sweep pays for a number nobody
# is reading. The YAML still says what the machine is; this says what
# this branch is doing.
#
# IT IS A FORCE RATHER THAN A DEFAULT because the default is per-config
# and there are nineteen of them: eighteen say 1, and a run that does
# not set TORCHSIM_DIR reads a DIFFERENT CHECKOUT's copy -- which is how
# the sweeps on this branch ran with timing on while this repo's default
# config said 0. Set TORCHSIM_TIMING_MODE=1 to get it back, and delete
# this arm when correctness work moves on.
env = os.environ.get("TORCHSIM_TIMING_MODE")
if env is not None:
return int(env)
return 0
# Sub-option of functional mode: compare every realized Spike buffer against a CPU
# golden to localize the first kernel whose value diverges. Auto-disabled when
# functional mode is off (there are no Spike values to verify).
Expand Down
106 changes: 98 additions & 8 deletions PyTorchSimFrontend/mlir/mlir_codegen_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,23 @@ def reduction_partial_combine_vec(reduction_type, vector_value, init_value):
return ops.logical_or(vector_value, init_value)
raise AssertionError(reduction_type)

def _fverify_writes(kernel_name, position):
"""Does `kernel_name` write the tensor argument at `position`?

The roles are recorded at define_kernel by the Triton backend, which is
the only route that has them; the MLIR route records nothing and every
argument stays checkable, which is what this did for both routes before.
Unknown kernel, unknown position, backend not imported -> True.
"""
if kernel_name is None:
return True
try:
from PyTorchSimFrontend.triton_backend import kernel_spec
except Exception: # noqa: BLE001 - the MLIR route need not have it
return True
return kernel_spec.writes_arg(kernel_name, position)


class ExtensionWrapperCodegen(wrapper.PythonWrapperCodegen):
def __init__(self):
super().__init__()
Expand Down Expand Up @@ -223,8 +240,18 @@ def generate(self, is_inference):
# result.splice(self.header)

self._fverify_seen = set()
self._fverify_last = None
with contextlib.ExitStack() as stack:
stack.enter_context(self.wrapper_call.indent())
# memory_plan_reuse() reaches self.estimate_peak through
# AllocateLine.should_reuse_buffer, and upstream sets it in
# run_wrapper_ir_passes -- which this override replaces, so nothing
# else will. Missing it is not a planning miss but an AttributeError,
# and only on a graph with a reuse candidate far enough back to need
# the estimate: ResNet-18 hits it, add does not. Same guard upstream
# uses, so buffer reuse off means no estimate to build.
if torch._inductor.config.allow_buffer_reuse:
self.estimate_peak = wrapper.EfficientPeakEstimate()
self.memory_plan_reuse()
with self.set_writeline(self.wrapper_call.writeline):
for line in self.lines:
Expand All @@ -240,7 +267,8 @@ def generate(self, is_inference):
elif isinstance(line, wrapper.KernelCallLine):
self.wrapper_call.writeline(self.wrap_kernel_call(line.kernel_name, line.call_args))
if _func_verify.enabled():
self._fverify_emit_checks(line.call_args)
self._fverify_emit_checks(line.call_args, id(line),
line.kernel_name)
else:
if isinstance(line, wrapper.WrapperLine):
line.codegen(self.wrapper_call)
Expand Down Expand Up @@ -270,21 +298,83 @@ def generate(self, is_inference):
self.kernel_declarations.getvaluewithlinemap(),
)

def _fverify_emit_checks(self, call_args):
def _fverify_last_writer(self):
"""{buffer name: id of the LAST kernel call that names it}.

THE FIRST KERNEL TO NAME A BUFFER IS NOT ALWAYS THE ONE THAT FINISHES
IT. One fx op can be split across several kernels, and then the buffer
is only complete after the last of them -- checking after the first
compares a half-built buffer against a finished golden and reports a
divergence that is not one.

measured DeepSeek-V3's MoE router. `aten.scatter.value` comes out
as two kernels sharing one origin node:

triton_npu_fused_scatter_zeros_like_38(buf9, 256)
_fverify.verify_check(buf9, ...) <- here
triton_npu_fused_scatter_zeros_like_39(buf8, buf9, 128)

38 writes the zeros and 39 scatters the ones, so the
check saw an all-zero buffer and reported "128/256
elements over tol, all npu=0 cpu=1" -- every one of the
scattered ones "missing". Running kernel 39 standalone
against a torch reference gives max_abs_err 0.

So the walk is done twice: once to find where each buffer is last
written, and once to emit. Same order, same buffers, one check each --
only the position moves.
"""
last = {}
for line in self.lines:
if not isinstance(line, wrapper.KernelCallLine):
continue
for pos, a in enumerate(line.call_args):
if not (isinstance(a, str) and a.strip().isidentifier()):
continue
if not _fverify_writes(line.kernel_name, pos):
continue
last[a.strip()] = id(line)
return last

def _fverify_emit_checks(self, call_args, line_id=None, kernel_name=None):
"""Emit per-kernel CPU verify calls for this kernel's output buffers.

A buffer's value is produced by the first kernel that names it (producer
precedes consumers in topo order), so we check each bare-identifier buffer
arg the first time it is seen -- that occurrence is its output. The buffer
is mapped to its originating fx node (op) so the runtime check can compare
against the CPU golden keyed by that node.
Each bare-identifier buffer arg the kernel WRITES is checked once,
after the LAST kernel that writes it -- see _fverify_last_writer for
why not the first. The buffer is mapped to its originating fx node (op)
so the runtime check can compare against the CPU golden keyed by that
node.

WRITES, NOT NAMES. This used to check every bare-identifier argument,
inputs included, and the docstrings on both halves said "writes" while
the code said "names". The two part company under buffer REUSE: the
wrapper renames storage (`buf20 = buf9 # reuse`), so one buffer's
contents live under another buffer's name, and that name's
`origin_node` describes what Inductor MEANT to put there. Check it
after a kernel that only reads it and the comparison is against a value
nothing computed.

measured Stable Diffusion v1.5's UNet. Two kernels take an
`in_out_ptr0` and never store to it; they are called
eight times between them, on buf20, buf88, buf104,
buf170, buf189, buf208, buf230 and buf298 -- and those
eight are EXACTLY the eight divergences the run
reported, each against an `add_N` node that no kernel
materialises. Nothing else in the model diverges; 217
kernels run between them.
"""
for a in call_args:
if self._fverify_last is None:
self._fverify_last = self._fverify_last_writer()
for pos, a in enumerate(call_args):
if not isinstance(a, str):
continue
name = a.strip()
if not name.isidentifier() or name in self._fverify_seen:
continue
if not _fverify_writes(kernel_name, pos):
continue # this kernel only reads it
if line_id is not None and self._fverify_last.get(name) != line_id:
continue # a later kernel still writes this buffer
self._fverify_seen.add(name)
if name in V.graph.graph_inputs:
continue # placeholders: golden == input, nothing to verify
Expand Down
Loading