已合并
[fix][inductor] fix master triton acc-check bug #43556
lv-kaimeng创建于 8月1日
[fix][inductor] fix master triton acc-check bug #43556
已合并
共 9 个文件变更+266-29
| @@ -318,9 +318,9 @@ def _patch_baseview_realize_hint(self): | |||
| 318 | else: | 318 | else: |
| 319 | return self.data.realize_hint() | 319 | return self.data.realize_hint() |
| 320 | 320 | ||
| 321 | -def _patch_mark_reuse(self, users): | 321 | +def _patch_mark_reuse(self, users, **kwargs): |
| 322 | if hasattr(self, 'traced_graph') and self.traced_graph is not None: | 322 | if hasattr(self, 'traced_graph') and self.traced_graph is not None: |
| 323 | - r = self.data.mark_reuse(users) | 323 | + r = self.data.mark_reuse(users, **kwargs) |
| 324 | buffer = try_get_buffer(self) | 324 | buffer = try_get_buffer(self) |
| 325 | if not buffer: | 325 | if not buffer: |
| 326 | return r | 326 | return r |
| @@ -340,7 +340,7 @@ def _patch_mark_reuse(self, users): | |||
| 340 | self._post_init_setattr("traced_graph", new_traced_graph) | 340 | self._post_init_setattr("traced_graph", new_traced_graph) |
| 341 | return r | 341 | return r |
| 342 | else: | 342 | else: |
| 343 | - return self.data.mark_reuse(users) | 343 | + return self.data.mark_reuse(users, **kwargs) |
| 344 | 344 | ||
| 345 | 345 | ||
| 346 | def _patch_expandview_create(cls, x, new_size, traced_graph=None, node_name=None): | 346 | def _patch_expandview_create(cls, x, new_size, traced_graph=None, node_name=None): |
| @@ -159,6 +159,7 @@ class NPUTritonScheduling(TritonScheduling): | |||
| 159 | V.graph.inplaced_to_remove |= kernel.inplaced_to_remove | 159 | V.graph.inplaced_to_remove |= kernel.inplaced_to_remove |
| 160 | 160 | ||
| 161 | traced_graph_hash = None | 161 | traced_graph_hash = None |
| 162 | + kernel_graph_hash = None | ||
| 162 | if npu_config.dump_fx_graph: | 163 | if npu_config.dump_fx_graph: |
| 163 | if not npu_config.traced_fx_graph_cache: | 164 | if not npu_config.traced_fx_graph_cache: |
| 164 | npu_config.traced_fx_graph_cache = os.path.join(os.getenv("TORCHINDUCTOR_CACHE_DIR"), | 165 | npu_config.traced_fx_graph_cache = os.path.join(os.getenv("TORCHINDUCTOR_CACHE_DIR"), |
| @@ -168,9 +169,11 @@ class NPUTritonScheduling(TritonScheduling): | |||
| 168 | if traced_graph is None: | 169 | if traced_graph is None: |
| 169 | log.warning("For nodes %s, could not gen fx graph while dump-graph.", nodes) | 170 | log.warning("For nodes %s, could not gen fx graph while dump-graph.", nodes) |
| 170 | else: | 171 | else: |
| 171 | - traced_graph_hash = code_hash(src_code + traced_graph.code) | 172 | + fx_arg_shapes_str = str([getattr(a, "shape", a) for a in fx_args]) |
| 173 | + traced_graph_hash = code_hash(src_code + traced_graph.code + fx_arg_shapes_str) | ||
| 174 | + kernel_graph_hash = code_hash(src_code + traced_graph.code) | ||
| 172 | 175 | ||
| 173 | - kernel_name, src_code = self.define_kernel(src_code, node_schedule, kernel, traced_graph_hash) | 176 | + kernel_name, src_code = self.define_kernel(src_code, node_schedule, kernel, traced_graph_hash, kernel_graph_hash) |
| 174 | 177 | ||
| 175 | kernel.kernel_name = kernel_name | 178 | kernel.kernel_name = kernel_name |
| 176 | kernel.code_hash = code_hash(src_code) | 179 | kernel.code_hash = code_hash(src_code) |
| @@ -366,10 +369,11 @@ class NPUTritonScheduling(TritonScheduling): | |||
| 366 | kernel_code_list.append((src_code, kernel, node_group)) | 369 | kernel_code_list.append((src_code, kernel, node_group)) |
| 367 | return kernel_code_list | 370 | return kernel_code_list |
| 368 | 371 | ||
| 369 | - def define_kernel(self, src_code, node_schedule, kernel, traced_graph_hash: str): | 372 | + def define_kernel(self, src_code, node_schedule, kernel, traced_graph_hash: str, kernel_graph_hash: str = None): |
| 370 | wrapper = V.graph.wrapper_code | 373 | wrapper = V.graph.wrapper_code |
| 371 | - if (src_code, traced_graph_hash) in wrapper.src_to_kernel: | 374 | + kernel_cache_key = (src_code, kernel_graph_hash) |
| 372 | - kernel_name = wrapper.src_to_kernel[(src_code, traced_graph_hash)] | 375 | + if kernel_cache_key in wrapper.src_to_kernel: |
| 376 | + kernel_name = wrapper.src_to_kernel[kernel_cache_key] | ||
| 373 | if npu_config.dump_fx_graph: | 377 | if npu_config.dump_fx_graph: |
| 374 | src_code = src_code.replace(str(Placeholder.DESCRIPTIVE_NAME), kernel_name) | 378 | src_code = src_code.replace(str(Placeholder.DESCRIPTIVE_NAME), kernel_name) |
| 375 | subs_name = kernel_name if config.triton.unique_kernel_names else "triton_" | 379 | subs_name = kernel_name if config.triton.unique_kernel_names else "triton_" |
| @@ -390,7 +394,7 @@ class NPUTritonScheduling(TritonScheduling): | |||
| 390 | ["triton", kernel_category, fused_name, wrapper.next_kernel_suffix()] | 394 | ["triton", kernel_category, fused_name, wrapper.next_kernel_suffix()] |
| 391 | ) | 395 | ) |
| 392 | # use the original src_code as the key | 396 | # use the original src_code as the key |
| 393 | - wrapper.src_to_kernel[(src_code, traced_graph_hash)] = kernel_name | 397 | + wrapper.src_to_kernel[kernel_cache_key] = kernel_name |
| 394 | subs_name = kernel_name if config.triton.unique_kernel_names else "triton_" | 398 | subs_name = kernel_name if config.triton.unique_kernel_names else "triton_" |
| 395 | 399 | ||
| 396 | # DESCRIPTIVE_NAME is used for profiling purposes; it shows the full kernel name | 400 | # DESCRIPTIVE_NAME is used for profiling purposes; it shows the full kernel name |
| @@ -5,6 +5,7 @@ import functools | |||
| 5 | import itertools | 5 | import itertools |
| 6 | import math | 6 | import math |
| 7 | import operator | 7 | import operator |
| 8 | +import os | ||
| 8 | import re | 9 | import re |
| 9 | from collections.abc import Callable, Iterable, Sequence | 10 | from collections.abc import Callable, Iterable, Sequence |
| 10 | from typing import Any, cast, Optional | 11 | from typing import Any, cast, Optional |
| @@ -4261,6 +4262,11 @@ class NPUIndexTritonKernel(TritonKernel): | |||
| 4261 | return sympy_var | 4262 | return sympy_var |
| 4262 | 4263 | ||
| 4263 | if var.bounds.lower < 0: # type: ignore[operator] | 4264 | if var.bounds.lower < 0: # type: ignore[operator] |
| 4265 | + if ( | ||
| 4266 | + os.environ.get("INDUCTOR_ASCEND_DUMP_FX_GRAPH") | ||
| 4267 | + or os.environ.get("INDUCTOR_ASCEND_CHECK_ACCURACY") | ||
| 4268 | + ): | ||
| 4269 | + wrap_neg = False | ||
| 4264 | if wrap_neg: | 4270 | if wrap_neg: |
| 4265 | stm = ops.add(var, ops.index_expr(size, torch.long)) | 4271 | stm = ops.add(var, ops.index_expr(size, torch.long)) |
| 4266 | # Mixed negative and non-negative | 4272 | # Mixed negative and non-negative |
| @@ -72,8 +72,10 @@ from torch._inductor.scheduler import Scheduler | |||
| 72 | 72 | ||
| 73 | from .codegen.triton_utils import NPUKernelType | 73 | from .codegen.triton_utils import NPUKernelType |
| 74 | from .ir import IndexputTemplate, ScatterTemplate | 74 | from .ir import IndexputTemplate, ScatterTemplate |
| 75 | -from .lowering_common import ( | 75 | +from .lowering_common import ( # noqa: F401 re-export for codegen/ir_fx.py |
| 76 | add_overload as _add_overload, | 76 | add_overload as _add_overload, |
| 77 | + create_fake_input, | ||
| 78 | + subtract_graph, | ||
| 77 | enable_full_lowering_fallback as enable_full_lowering_fallback_common, | 79 | enable_full_lowering_fallback as enable_full_lowering_fallback_common, |
| 78 | resolve_op_from_name, | 80 | resolve_op_from_name, |
| 79 | ) | 81 | ) |
| @@ -83,10 +85,15 @@ from .config import inductor_indirect_memory_mode, log, is_ascend950, enable_ful | |||
| 83 | from .lowering_fallback_list import FALLBACK_LIST, NPU_EXTRA_FALLBACK_LIST | 85 | from .lowering_fallback_list import FALLBACK_LIST, NPU_EXTRA_FALLBACK_LIST |
| 84 | 86 | ||
| 85 | from . import config as npu_config | 87 | from . import config as npu_config |
| 86 | -from .lowering_fx import ( | 88 | +from .lowering_fx import ( # noqa: F401 re-export for codegen/scheduling.py & ir_fx.py |
| 87 | fetch_graphs, | 89 | fetch_graphs, |
| 88 | merge_traced_graphs, | 90 | merge_traced_graphs, |
| 89 | node_id, | 91 | node_id, |
| 92 | + create_fx_from_snodes_by_traced_graph, | ||
| 93 | + create_compile_kwargs, | ||
| 94 | + generate_fx_graph_code, | ||
| 95 | + dump_fx_graph_code, | ||
| 96 | + snodes_to_fx, | ||
| 90 | ) | 97 | ) |
| 91 | 98 | ||
| 92 | def npu_make_fallback(op, layout_constraint=None, warn=True, override_decomp=False): | 99 | def npu_make_fallback(op, layout_constraint=None, warn=True, override_decomp=False): |
| @@ -323,7 +323,8 @@ def fetch_graphs( | |||
| 323 | *, | 323 | *, |
| 324 | use_npu_meta: bool = False, | 324 | use_npu_meta: bool = False, |
| 325 | ): | 325 | ): |
| 326 | - if isinstance(inputs, (TensorBox, ir.StorageBox, ir.View, sympy.Symbol, ir.Constant, ir.ReinterpretView)): | 326 | + if isinstance(inputs, (TensorBox, ir.StorageBox, ir.View, ir.ExpandView, ir.PermuteView, ir.SliceView, |
| 327 | + sympy.Symbol, ir.Constant, ir.ReinterpretView)): | ||
| 327 | inputs = [inputs] | 328 | inputs = [inputs] |
| 328 | input_graphs = [] | 329 | input_graphs = [] |
| 329 | for inp in inputs: | 330 | for inp in inputs: |
| @@ -351,7 +352,19 @@ def fetch_graphs( | |||
| 351 | continue | 352 | continue |
| 352 | name = inp.get_name() | 353 | name = inp.get_name() |
| 353 | traced_graph = inp.get_traced_graph() | 354 | traced_graph = inp.get_traced_graph() |
| 354 | - if traced_graph is not None: | 355 | + if ( |
| 356 | + traced_graph is not None | ||
| 357 | + and not isinstance(inp, ir.ConcatKernel) | ||
| 358 | + and not ( | ||
| 359 | + hasattr(inp, 'data') | ||
| 360 | + and isinstance(inp.data, ir.ConcatKernel) | ||
| 361 | + ) | ||
| 362 | + and not ( | ||
| 363 | + hasattr(inp, 'data') | ||
| 364 | + and hasattr(inp.data, 'data') | ||
| 365 | + and isinstance(inp.data.data, ir.ConcatKernel) | ||
| 366 | + ) | ||
| 367 | + ): | ||
| 355 | input_graphs.append(traced_graph) | 368 | input_graphs.append(traced_graph) |
| 356 | continue | 369 | continue |
| 357 | traced_graph = TracedGraph() | 370 | traced_graph = TracedGraph() |
| @@ -445,11 +445,16 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args): | |||
| 445 | fx_call_args[idx] = final_kernel.args.inplace_buffers[call_arg].other_names[-1] | 445 | fx_call_args[idx] = final_kernel.args.inplace_buffers[call_arg].other_names[-1] |
| 446 | fx_arg_shapes = [fx_arg.shape if isinstance(fx_arg, torch.Tensor) else ['ushape'] for fx_arg in fx_args] | 446 | fx_arg_shapes = [fx_arg.shape if isinstance(fx_arg, torch.Tensor) else ['ushape'] for fx_arg in fx_args] |
| 447 | 447 | ||
| 448 | + # add_numel 前移: 让 numel 表达式参与后续匹配, SymInt 标量输入有机会匹配到 kernel numel arg | ||
| 449 | + final_kernel.add_numel_to_call_args(final_kernel.kernel_name, kernel_call_args, arg_types) | ||
| 450 | + # add_numel 后 kernel_call_args 可能含 SymbolicCallArg (unhashable), 统一用 str 比较 | ||
| 451 | + kernel_call_args_str = [str(arg) for arg in kernel_call_args] | ||
| 452 | + | ||
| 448 | fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))] | 453 | fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))] |
| 449 | 454 | ||
| 450 | - if set(kernel_call_args) != set(fx_call_args_no_dy): | 455 | + if set(kernel_call_args_str) != set(fx_call_args_no_dy): |
| 451 | # Handle NonOwningLayout alias | 456 | # Handle NonOwningLayout alias |
| 452 | - kernel_call_args_set = set(kernel_call_args) | 457 | + kernel_call_args_set = set(kernel_call_args_str) |
| 453 | for idx, call_arg in enumerate(fx_call_args): | 458 | for idx, call_arg in enumerate(fx_call_args): |
| 454 | if call_arg in kernel_call_args_set: | 459 | if call_arg in kernel_call_args_set: |
| 455 | continue | 460 | continue |
| @@ -462,9 +467,22 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args): | |||
| 462 | break | 467 | break |
| 463 | 468 | ||
| 464 | fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))] | 469 | fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))] |
| 465 | - if set(kernel_call_args) != set(fx_call_args_no_dy): | 470 | + kernel_call_args_str_set = set(kernel_call_args_str) |
| 466 | - return None | 471 | + if kernel_call_args_str_set != set(fx_call_args_no_dy): |
| 467 | - final_kernel.add_numel_to_call_args(final_kernel.kernel_name, kernel_call_args, arg_types) | 472 | + # 区分: 不匹配的是 "标量输入" (可恢复) 还是 "buffer" (应放弃) |
| 473 | + fx_only = set(fx_call_args_no_dy) - kernel_call_args_str_set | ||
| 474 | + scalar_only_mismatch = True | ||
| 475 | + for buf_name in fx_only: | ||
| 476 | + buf = V.graph.try_get_buffer(buf_name) | ||
| 477 | + if buf is not None: | ||
| 478 | + scalar_only_mismatch = False | ||
| 479 | + break | ||
| 480 | + if not scalar_only_mismatch: | ||
| 481 | + log.warning("[ACC-DEBUG] kernel=%s skipped: buffer mismatch (fx_only=%s)", | ||
| 482 | + final_kernel.kernel_name, fx_only) | ||
| 483 | + return None | ||
| 484 | + # scalar_only_mismatch: SymInt 标量输入没有对应 kernel arg, 用 -1 哨兵保留位置, | ||
| 485 | + # 运行期从输出 tensor shape 反推其值 (见 npu_compare.check_accuracy_triton) | ||
| 468 | 486 | ||
| 469 | kernel_call_args = [ | 487 | kernel_call_args = [ |
| 470 | map_operators_to_strings(str(item.inner_expr)) if isinstance(item, torch._inductor.codegen.wrapper.SymbolicCallArg) | 488 | map_operators_to_strings(str(item.inner_expr)) if isinstance(item, torch._inductor.codegen.wrapper.SymbolicCallArg) |
| @@ -472,11 +490,75 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args): | |||
| 472 | for item in kernel_call_args | 490 | for item in kernel_call_args |
| 473 | ] | 491 | ] |
| 474 | index_map = {str(element): idx for idx, element in enumerate(kernel_call_args)} | 492 | index_map = {str(element): idx for idx, element in enumerate(kernel_call_args)} |
| 475 | - call_args_mapping = [index_map[str(element)] for element in fx_call_args if str(element) in index_map] | 493 | + # Prefer the runtime kernel argument for every exact match, including |
| 494 | + # symbolic scalar arguments such as a numel expression. | ||
| 495 | + call_args_mapping = [] | ||
| 496 | + for i, element in enumerate(fx_call_args): | ||
| 497 | + element_str = str(element) | ||
| 498 | + if element_str in index_map: | ||
| 499 | + call_args_mapping.append(index_map[element_str]) | ||
| 500 | + elif i < len(fx_args) and not isinstance(fx_args[i], torch.Tensor): | ||
| 501 | + # Only scalars absent from the kernel signature need a shape recipe. | ||
| 502 | + call_args_mapping.append(-1) | ||
| 503 | + else: | ||
| 504 | + return None | ||
| 505 | + | ||
| 506 | + # 精确配方: 对每个 -1 标量输入, 编译期求解它等于哪个 kernel arg tensor 的第几维。 | ||
| 507 | + # 序列化为具体 int (kernel_arg_idx, dim), 运行期 args[kidx].shape[dim] 精确反推, | ||
| 508 | + # 不依赖 "FX 标量位置 == 输出维度序号" 的假设 (比 shape[pos] 猜测更普适)。 | ||
| 509 | + def _to_sympy_expr(val): | ||
| 510 | + if val is None: | ||
| 511 | + return None | ||
| 512 | + if isinstance(val, (int, sympy.Integer)): | ||
| 513 | + return sympy.sympify(val) | ||
| 514 | + if hasattr(val, "_sympy_"): | ||
| 515 | + try: | ||
| 516 | + return val._sympy_() | ||
| 517 | + except Exception: | ||
| 518 | + return None | ||
| 519 | + try: | ||
| 520 | + return sympy.sympify(val) | ||
| 521 | + except Exception: | ||
| 522 | + return None | ||
| 523 | + | ||
| 524 | + scalar_resolved_from = {} | ||
| 525 | + for i, idx in enumerate(call_args_mapping): | ||
| 526 | + if idx != -1: | ||
| 527 | + continue | ||
| 528 | + scalar_expr = _to_sympy_expr(fx_args[i]) | ||
| 529 | + if scalar_expr is None: | ||
| 530 | + return None | ||
| 531 | + found = None | ||
| 532 | + for j, fa in enumerate(fx_args): | ||
| 533 | + if not isinstance(fa, torch.Tensor): | ||
| 534 | + continue | ||
| 535 | + kidx = call_args_mapping[j] | ||
| 536 | + if kidx < 0: | ||
| 537 | + continue | ||
| 538 | + for d, dim_val in enumerate(fa.shape): | ||
| 539 | + dim_expr = _to_sympy_expr(dim_val) | ||
| 540 | + if dim_expr is None: | ||
| 541 | + continue | ||
| 542 | + if scalar_expr == dim_expr or sympy.simplify(scalar_expr - dim_expr) == 0: | ||
| 543 | + found = (kidx, d) | ||
| 544 | + break | ||
| 545 | + if found is not None: | ||
| 546 | + break | ||
| 547 | + if found is None: | ||
| 548 | + # 无法从任何 tensor 的 shape 反推该 SymInt 标量 → 跳过该 kernel 精度检查 (安全, 不猜) | ||
| 549 | + log.warning("[ACC-DEBUG] kernel=%s skipped: cannot resolve scalar at pos=%s from any tensor shape", | ||
| 550 | + final_kernel.kernel_name, i) | ||
| 551 | + return None | ||
| 552 | + scalar_resolved_from[i] = found | ||
| 476 | mismatch_indices_shapes = {} | 553 | mismatch_indices_shapes = {} |
| 477 | 554 | ||
| 478 | def is_dynamic_shape_dim(d): | 555 | def is_dynamic_shape_dim(d): |
| 479 | - if str(type(d)).find("torch.fx.experimental.symbolic_shapes") != -1: | 556 | + type_str = str(type(d)) |
| 557 | + if type_str.find("torch.fx.experimental.symbolic_shapes") != -1: | ||
| 558 | + return True | ||
| 559 | + if type_str.find("torch.SymInt") != -1: | ||
| 560 | + return True | ||
| 561 | + if type_str.find("sympy") != -1: | ||
| 480 | return True | 562 | return True |
| 481 | s = str(d).strip() | 563 | s = str(d).strip() |
| 482 | if s.startswith("u") or s.startswith("i") or s in ("-1", "?"): | 564 | if s.startswith("u") or s.startswith("i") or s in ("-1", "?"): |
| @@ -487,15 +569,28 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args): | |||
| 487 | return True | 569 | return True |
| 488 | 570 | ||
| 489 | for i in range(len(fx_call_args)): | 571 | for i in range(len(fx_call_args)): |
| 490 | - if any(is_dynamic_shape_dim(dim) for dim in fx_arg_shapes[i]): | 572 | + if not isinstance(fx_args[i], torch.Tensor): |
| 491 | continue | 573 | continue |
| 492 | - mismatch_indices_shapes[i] = fx_arg_shapes[i] | 574 | + reshape_shape = [] |
| 575 | + dynamic_dim_count = 0 | ||
| 576 | + for dim in fx_arg_shapes[i]: | ||
| 577 | + if is_dynamic_shape_dim(dim): | ||
| 578 | + dynamic_dim_count += 1 | ||
| 579 | + reshape_shape.append(-1) | ||
| 580 | + else: | ||
| 581 | + reshape_shape.append(int(dim)) | ||
| 582 | + # torch.reshape can infer one dynamic dimension from the runtime numel. | ||
| 583 | + if dynamic_dim_count > 1: | ||
| 584 | + continue | ||
| 585 | + mismatch_indices_shapes[i] = torch.Size(reshape_shape) | ||
| 493 | 586 | ||
| 494 | return { | 587 | return { |
| 495 | "call_args_mapping": call_args_mapping, | 588 | "call_args_mapping": call_args_mapping, |
| 496 | "mismatch_indices_shapes": mismatch_indices_shapes, | 589 | "mismatch_indices_shapes": mismatch_indices_shapes, |
| 590 | + "scalar_resolved_from": scalar_resolved_from, | ||
| 497 | } | 591 | } |
| 498 | 592 | ||
| 593 | + | ||
| 499 | def generate_fx_graph_code(code, kernel_code, kernel_name, compile_kwargs): | 594 | def generate_fx_graph_code(code, kernel_code, kernel_name, compile_kwargs): |
| 500 | code = textwrap.indent(code, ' ') | 595 | code = textwrap.indent(code, ' ') |
| 501 | code_template = f""" | 596 | code_template = f""" |
| @@ -546,6 +641,7 @@ num_inputs = {compile_kwargs['num_inputs']} | |||
| 546 | num_outputs = {compile_kwargs['num_outputs']} | 641 | num_outputs = {compile_kwargs['num_outputs']} |
| 547 | non_contiguous_indices = {compile_kwargs['non_contiguous_indices']} | 642 | non_contiguous_indices = {compile_kwargs['non_contiguous_indices']} |
| 548 | mismatch_indices_shapes = {compile_kwargs['mismatch_indices_shapes']} | 643 | mismatch_indices_shapes = {compile_kwargs['mismatch_indices_shapes']} |
| 644 | +scalar_resolved_from = {compile_kwargs.get('scalar_resolved_from', {})} | ||
| 549 | 645 | ||
| 550 | async_compile = AsyncCompile() | 646 | async_compile = AsyncCompile() |
| 551 | {kernel_name} = async_compile.triton('{kernel_name}', ''' | 647 | {kernel_name} = async_compile.triton('{kernel_name}', ''' |
| @@ -567,7 +663,12 @@ def run(): | |||
| 567 | args = [arg.npu() if isinstance(arg, torch.Tensor) else arg for arg in args] | 663 | args = [arg.npu() if isinstance(arg, torch.Tensor) else arg for arg in args] |
| 568 | 664 | ||
| 569 | fx_args = [] | 665 | fx_args = [] |
| 570 | - for idx in call_args_mapping: | 666 | + for pos, idx in enumerate(call_args_mapping): |
| 667 | + if idx == -1: | ||
| 668 | + # SymInt 标量输入: 用编译期记录的精确配方 (kernel_arg_idx, dim) 反推 | ||
| 669 | + kidx, d = scalar_resolved_from[pos] | ||
| 670 | + fx_args.append(int(args[kidx].shape[d])) | ||
| 671 | + continue | ||
| 571 | arg = args[idx] | 672 | arg = args[idx] |
| 572 | if isinstance(arg, int): | 673 | if isinstance(arg, int): |
| 573 | fx_args.append(arg) | 674 | fx_args.append(arg) |
| @@ -7,10 +7,10 @@ | |||
| 7 | # independently. | 7 | # independently. |
| 8 | 8 | ||
| 9 | from __future__ import annotations | 9 | from __future__ import annotations |
| 10 | - | ||
| 11 | import copy | 10 | import copy |
| 12 | import functools | 11 | import functools |
| 13 | import importlib | 12 | import importlib |
| 13 | +import os | ||
| 14 | from collections.abc import Iterable, Mapping | 14 | from collections.abc import Iterable, Mapping |
| 15 | from dataclasses import dataclass, field | 15 | from dataclasses import dataclass, field |
| 16 | from typing import Any, Callable, Optional | 16 | from typing import Any, Callable, Optional |
| @@ -156,6 +156,15 @@ def _uses_device_lowering( | |||
| 156 | ) | 156 | ) |
| 157 | if layout_device_type is not None: | 157 | if layout_device_type is not None: |
| 158 | return layout_device_type == device_type | 158 | return layout_device_type == device_type |
| 159 | + if os.environ.get("INDUCTOR_ASCEND_DUMP_FX_GRAPH") or os.environ.get("INDUCTOR_ASCEND_CHECK_ACCURACY"): | ||
| 160 | + device_kwarg = kwargs.get("device") | ||
| 161 | + if isinstance(device_kwarg, (torch.device, str)): | ||
| 162 | + try: | ||
| 163 | + kwarg_device_type = torch.device(device_kwarg).type | ||
| 164 | + except Exception: | ||
| 165 | + kwarg_device_type = None | ||
| 166 | + if kwarg_device_type is not None: | ||
| 167 | + return kwarg_device_type in ("npu", "cpu") | ||
| 159 | if device_type in _iter_ir_device_types((args, kwargs)): | 168 | if device_type in _iter_ir_device_types((args, kwargs)): |
| 160 | return True | 169 | return True |
| 161 | return extra_device_predicate is not None and extra_device_predicate( | 170 | return extra_device_predicate is not None and extra_device_predicate( |
| @@ -1,12 +1,19 @@ | |||
| 1 | import importlib | 1 | import importlib |
| 2 | +import logging | ||
| 2 | import os | 3 | import os |
| 3 | import sys | 4 | import sys |
| 5 | +from collections import defaultdict | ||
| 4 | from typing import Any, Iterable, Mapping | 6 | from typing import Any, Iterable, Mapping |
| 5 | 7 | ||
| 6 | import torch | 8 | import torch |
| 7 | from torch._inductor.compile_fx import clone_preserve_strides | 9 | from torch._inductor.compile_fx import clone_preserve_strides |
| 8 | 10 | ||
| 9 | 11 | ||
| 12 | +log = logging.getLogger(__name__) | ||
| 13 | + | ||
| 14 | +_call_counter: dict = defaultdict(int) | ||
| 15 | + | ||
| 16 | + | ||
| 10 | def clone_for_accuracy(arg): | 17 | def clone_for_accuracy(arg): |
| 11 | if not isinstance(arg, torch.Tensor): | 18 | if not isinstance(arg, torch.Tensor): |
| 12 | return arg | 19 | return arg |
| @@ -51,14 +58,27 @@ def _report_mismatch(idx, actual, expected, matches, rtol, atol, kernel_name, du | |||
| 51 | rel_diff.masked_fill_(matches, 0) | 58 | rel_diff.masked_fill_(matches, 0) |
| 52 | number_of_elements = matches.numel() | 59 | number_of_elements = matches.numel() |
| 53 | total_mismatches = number_of_elements - int(torch.sum(matches)) | 60 | total_mismatches = number_of_elements - int(torch.sum(matches)) |
| 61 | + mismatch_mask = ~matches | ||
| 62 | + mismatch_indices = torch.nonzero(mismatch_mask.flatten())[:5].reshape(-1) | ||
| 63 | + sample_info = "" | ||
| 64 | + if mismatch_indices.numel() > 0: | ||
| 65 | + idx_flat = mismatch_indices[:3].tolist() | ||
| 66 | + idx_unflat = [list(torch.unravel_index(torch.tensor(i), actual.shape)) for i in idx_flat] | ||
| 67 | + samples = "; ".join( | ||
| 68 | + f"pos{idx_unflat[j]}: actual={actual.flatten()[i].item():.6e}, expected={expected.flatten()[i].item():.6e}" | ||
| 69 | + for j, i in enumerate(idx_flat) | ||
| 70 | + ) | ||
| 71 | + sample_info = f", Sample values: [{samples}]" | ||
| 54 | msg = ( | 72 | msg = ( |
| 55 | "CHECK ACCURACY FAILED! " | 73 | "CHECK ACCURACY FAILED! " |
| 56 | f"Kernel: {kernel_name}, Output idx: {idx}, " | 74 | f"Kernel: {kernel_name}, Output idx: {idx}, " |
| 75 | + f"actual_shape={list(actual.shape)}, actual_dtype={actual.dtype}, " | ||
| 76 | + f"expected_shape={list(expected.shape)}, expected_dtype={expected.dtype}, " | ||
| 57 | f"Mismatched: {total_mismatches}/{number_of_elements} " | 77 | f"Mismatched: {total_mismatches}/{number_of_elements} " |
| 58 | f"({total_mismatches / number_of_elements:.1%}), " | 78 | f"({total_mismatches / number_of_elements:.1%}), " |
| 59 | f"Greatest Rel Diff: {rel_diff.max().item()}, " | 79 | f"Greatest Rel Diff: {rel_diff.max().item()}, " |
| 60 | f"Greatest Abs Diff: {abs_diff.max().item()}, " | 80 | f"Greatest Abs Diff: {abs_diff.max().item()}, " |
| 61 | - f"rtol: {rtol}, atol: {atol}" | 81 | + f"rtol: {rtol}, atol: {atol}{sample_info}" |
| 62 | ) | 82 | ) |
| 63 | if dump_path: | 83 | if dump_path: |
| 64 | msg += f", dump_path: {dump_path}" | 84 | msg += f", dump_path: {dump_path}" |
| @@ -117,8 +137,23 @@ def check_accuracy_triton(*args, launcher, grid, stream, inductor_meta, **kwargs | |||
| 117 | return None | 137 | return None |
| 118 | call_outputs_indices = fx_module.call_args_mapping[fx_module.num_inputs:] | 138 | call_outputs_indices = fx_module.call_args_mapping[fx_module.num_inputs:] |
| 119 | 139 | ||
| 140 | + _call_counter[kernel_name] += 1 | ||
| 141 | + invocation = _call_counter[kernel_name] | ||
| 142 | + scalar_resolved_from = getattr(fx_module, "scalar_resolved_from", {}) | ||
| 143 | + | ||
| 144 | + # 诊断: 记录配方路径的解析值, 供 compare_outputs 失败时打印 (区分误报) | ||
| 145 | + _recipe_used = bool(scalar_resolved_from) | ||
| 146 | + _recipe_resolved = {} | ||
| 120 | fx_args = [] | 147 | fx_args = [] |
| 121 | - for idx in fx_module.call_args_mapping: | 148 | + for pos, idx in enumerate(fx_module.call_args_mapping): |
| 149 | + if idx == -1: | ||
| 150 | + # SymInt 标量输入: 用编译期记录的精确配方 (kernel_arg_idx, dim) 反推其值。 | ||
| 151 | + # 配方在 create_compile_kwargs 求解, 序列化为具体 int, 不依赖 pos==dim 假设。 | ||
| 152 | + kidx, d = scalar_resolved_from[pos] | ||
| 153 | + _resolved = int(args[kidx].shape[d]) | ||
| 154 | + _recipe_resolved[pos] = (scalar_resolved_from[pos], _resolved) | ||
| 155 | + fx_args.append(_resolved) | ||
| 156 | + continue | ||
| 122 | arg = args[idx] | 157 | arg = args[idx] |
| 123 | if isinstance(arg, int): | 158 | if isinstance(arg, int): |
| 124 | fx_args.append(arg) | 159 | fx_args.append(arg) |
| @@ -130,11 +165,24 @@ def check_accuracy_triton(*args, launcher, grid, stream, inductor_meta, **kwargs | |||
| 130 | arg) | 165 | arg) |
| 131 | fx_args.append(fx_arg) | 166 | fx_args.append(fx_arg) |
| 132 | 167 | ||
| 168 | + input_indices = fx_module.call_args_mapping[:fx_module.num_inputs] | ||
| 169 | + input_shapes = {f"args[{i}]": list(args[i].shape) if isinstance(args[i], torch.Tensor) else type(args[i]).__name__ | ||
| 170 | + for i in input_indices} | ||
| 171 | + output_indices = call_outputs_indices | ||
| 172 | + output_shapes = {f"args[{i}]": list(args[i].shape) if isinstance(args[i], torch.Tensor) else type(args[i]).__name__ | ||
| 173 | + for i in output_indices} | ||
| 174 | + log.debug("[ACC_DEBUG] kernel=%s invocation=#%s inputs=%s outputs=%s", | ||
| 175 | + kernel_name, invocation, input_shapes, output_shapes) | ||
| 176 | + _input_snapshot = {} | ||
| 177 | + for idx in fx_module.call_args_mapping[:fx_module.num_inputs]: | ||
| 178 | + if isinstance(args[idx], torch.Tensor): | ||
| 179 | + _input_snapshot[idx] = args[idx].cpu() | ||
| 180 | + | ||
| 133 | fx_graph_call(*fx_args) | 181 | fx_graph_call(*fx_args) |
| 134 | 182 | ||
| 135 | launcher(*args, **kwargs, stream=stream) | 183 | launcher(*args, **kwargs, stream=stream) |
| 136 | 184 | ||
| 137 | - compare_outputs( | 185 | + passed = compare_outputs( |
| 138 | [args[i] for i in call_outputs_indices], | 186 | [args[i] for i in call_outputs_indices], |
| 139 | fx_args[fx_module.num_inputs:], | 187 | fx_args[fx_module.num_inputs:], |
| 140 | kernel_name=kernel_name, | 188 | kernel_name=kernel_name, |
| @@ -142,6 +190,33 @@ def check_accuracy_triton(*args, launcher, grid, stream, inductor_meta, **kwargs | |||
| 142 | dump_path=dump_path, | 190 | dump_path=dump_path, |
| 143 | ) | 191 | ) |
| 144 | 192 | ||
| 193 | + if not passed and dump_path: | ||
| 194 | + fail_path = os.path.join(dump_path, f'data_fail_{invocation}.pth') | ||
| 195 | + fail_args = list(args) | ||
| 196 | + for idx, cpu_tensor in _input_snapshot.items(): | ||
| 197 | + fail_args[idx] = cpu_tensor | ||
| 198 | + torch.save(fail_args, fail_path) | ||
| 199 | + log.warning("[ACC_DEBUG] kernel=%s invocation=#%s FAIL: saved input snapshot to data_fail_%s.pth", | ||
| 200 | + kernel_name, invocation, invocation) | ||
| 201 | + | ||
| 202 | + if not passed and _recipe_used: | ||
| 203 | + kernel_int_values = [arg for arg in args if isinstance(arg, int)] | ||
| 204 | + for pos, ((kidx, d), val) in _recipe_resolved.items(): | ||
| 205 | + if val in kernel_int_values: | ||
| 206 | + log.warning( | ||
| 207 | + "[ACC_DEBUG] kernel=%s FAIL: scalar pos=%s resolved=%s " | ||
| 208 | + "(from args[%s].shape[%s]), matches kernel int arg. " | ||
| 209 | + "Likely a real kernel bug. dump_path=%s", | ||
| 210 | + kernel_name, pos, val, kidx, d, dump_path, | ||
| 211 | + ) | ||
| 212 | + else: | ||
| 213 | + log.warning( | ||
| 214 | + "[ACC_DEBUG] kernel=%s FAIL: scalar pos=%s resolved=%s " | ||
| 215 | + "(from args[%s].shape[%s]), NOT found in kernel int args %s. " | ||
| 216 | + "Likely a tool artifact (scalar resolution mismatch). dump_path=%s", | ||
| 217 | + kernel_name, pos, val, kidx, d, kernel_int_values, dump_path, | ||
| 218 | + ) | ||
| 219 | + | ||
| 145 | for arg in fx_args: | 220 | for arg in fx_args: |
| 146 | del arg | 221 | del arg |
| 147 | return True | 222 | return True |
| @@ -1790,8 +1790,23 @@ class NPUCachingAutotuner(CachingAutotuner): | |||
| 1790 | if dump_path is None: | 1790 | if dump_path is None: |
| 1791 | log.warning("data dump for kernel %s failed, no valid dump_path is supplied.", self.get_fn_name()) | 1791 | log.warning("data dump for kernel %s failed, no valid dump_path is supplied.", self.get_fn_name()) |
| 1792 | return False | 1792 | return False |
| 1793 | + if not hasattr(self, '_dump_counter'): | ||
| 1794 | + self._dump_counter = 0 | ||
| 1795 | + | ||
| 1793 | data_dump_path = os.path.join(dump_path, 'data.pth') | 1796 | data_dump_path = os.path.join(dump_path, 'data.pth') |
| 1794 | - torch.save(args, data_dump_path) | 1797 | + input_info = { |
| 1798 | + i: list(arg.shape) if isinstance(arg, torch.Tensor) else type(arg).__name__ | ||
| 1799 | + for i, arg in enumerate(args) | ||
| 1800 | + } | ||
| 1801 | + | ||
| 1802 | + if self._dump_counter == 0: | ||
| 1803 | + log.info("[ACC_DEBUG] %s invocation=#1 saving args=%s to data.pth", | ||
| 1804 | + self.get_fn_name(), input_info) | ||
| 1805 | + torch.save(args, data_dump_path) | ||
| 1806 | + self._dump_counter += 1 | ||
| 1807 | + else: | ||
| 1808 | + log.debug("[ACC_DEBUG] %s invocation=#%s skip save (data.pth preserved from invocation #1, args=%s)", | ||
| 1809 | + self.get_fn_name(), self._dump_counter + 1, input_info) | ||
| 1795 | return True | 1810 | return True |
| 1796 | 1811 | ||
| 1797 | def get_fn_name(self): | 1812 | def get_fn_name(self): |
| @@ -1820,7 +1835,14 @@ class NPUCachingAutotuner(CachingAutotuner): | |||
| 1820 | "Please disable aclgraph before enabling INDUCTOR_ASCEND_CHECK_ACCURACY " | 1835 | "Please disable aclgraph before enabling INDUCTOR_ASCEND_CHECK_ACCURACY " |
| 1821 | "/ INDUCTOR_ASCEND_DUMP_FX_GRAPH, or unset these environment variables." | 1836 | "/ INDUCTOR_ASCEND_DUMP_FX_GRAPH, or unset these environment variables." |
| 1822 | ) | 1837 | ) |
| 1823 | - _ = self.data_dump(*args) | 1838 | + # Strip runtime_block args from the end before saving for replay. |
| 1839 | + # data_dump receives *launch_args (= kernel_args + runtime_blocks), | ||
| 1840 | + # but the replayed script will call run() which re-appends runtime_blocks | ||
| 1841 | + # via _build_runtime_launch_args. Saving only kernel_args avoids a | ||
| 1842 | + # double-append that overflows positional parameters into "stream". | ||
| 1843 | + num_rb = len(self.runtime_block_arg_names or ()) | ||
| 1844 | + args_for_dump = args[:-num_rb] if num_rb else args | ||
| 1845 | + _ = self.data_dump(*args_for_dump) | ||
| 1824 | 1846 | ||
| 1825 | if npu_config.check_accuracy: | 1847 | if npu_config.check_accuracy: |
| 1826 | if check_accuracy_triton( | 1848 | if check_accuracy_triton( |