From e1557685177d4b67f6f1ac4fe90e0d812003160a Mon Sep 17 00:00:00 2001 From: Dayuxiaoshui <792179245@qq.com> Date: Wed, 12 Nov 2025 15:49:17 +0800 Subject: [PATCH 1/3] feat: support direct log file parsing in analysis scripts - Add parse_logs_to_data() function to directly parse log files without intermediate JSON - Update scan_all_folders() to automatically detect and handle log files - Support both Paddle (with subgraph) and PyTorch (without subgraph) samples - Update plot_ESt.py and plot_St.py help documentation - Remove dependency on log2json intermediate step for analysis workflow --- graph_net/analysis_util.py | 205 +++++++++++++++++- graph_net/plot_ESt.py | 2 +- graph_net/plot_St.py | 2 +- .../test/chain_naive_graph_decomposer_test.sh | 2 +- graph_net/test/decomposer_validator_test.sh | 51 +++++ graph_net/test/naive_graph_decomposer_test.sh | 2 +- .../range_decomposer_validator_backend.py | 76 +++++++ .../backend/unstable_to_stable_backend.py | 90 +++++++- graph_net/torch/fx_graph_serialize_util.py | 9 +- graph_net/torch/test_compiler.py | 4 +- .../Qwen1.5-0.5B/graph_net.json | 4 +- .../graph_net.json | 7 + .../input_meta.py | 0 .../range_decomposer_validator/__init__.py | 0 .../range_decomposer_validator.py | 91 -------- .../test/simple_CNN/graph_hash.txt | 1 - .../test/simple_CNN/graph_net.json | 0 .../simple_CNN/input_tensor_constraints.py | 0 .../test/simple_CNN/model.py | 92 -------- .../test/simple_CNN/weight_meta.py | 88 -------- .../subgraph_0/graph_net.json | 0 .../subgraph_0/input_meta.py | 0 .../subgraph_0/input_tensor_constraints.py | 0 .../simple_CNN_decomposed/subgraph_0/model.py | 36 --- .../subgraph_0/weight_meta.py | 28 --- .../subgraph_1/graph_net.json | 0 .../subgraph_1/input_meta.py | 0 .../subgraph_1/input_tensor_constraints.py | 0 .../simple_CNN_decomposed/subgraph_1/model.py | 35 --- .../subgraph_1/weight_meta.py | 29 --- .../subgraph_2/graph_net.json | 0 .../subgraph_2/input_meta.py | 0 .../subgraph_2/input_tensor_constraints.py | 0 .../simple_CNN_decomposed/subgraph_2/model.py | 41 ---- .../subgraph_2/weight_meta.py | 49 ----- tools/check_and_count_samples.py | 139 ++++++++++++ tools/ci/check_validate.sh | 5 +- tools/count_sample.py | 40 ---- 38 files changed, 576 insertions(+), 552 deletions(-) create mode 100644 graph_net/test/decomposer_validator_test.sh create mode 100644 graph_net/torch/backend/range_decomposer_validator_backend.py create mode 100644 samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli/graph_net.json rename {todo_works/range_decomposer_validator/test/simple_CNN => samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli}/input_meta.py (100%) delete mode 100644 todo_works/range_decomposer_validator/__init__.py delete mode 100644 todo_works/range_decomposer_validator/range_decomposer_validator.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN/graph_hash.txt delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN/graph_net.json delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN/input_tensor_constraints.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN/model.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN/weight_meta.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/graph_net.json delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/input_meta.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/input_tensor_constraints.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/model.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/weight_meta.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/graph_net.json delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/input_meta.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/input_tensor_constraints.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/model.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/weight_meta.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/graph_net.json delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/input_meta.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/input_tensor_constraints.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/model.py delete mode 100644 todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/weight_meta.py create mode 100644 tools/check_and_count_samples.py delete mode 100644 tools/count_sample.py diff --git a/graph_net/analysis_util.py b/graph_net/analysis_util.py index 98342dd18..e4f192391 100644 --- a/graph_net/analysis_util.py +++ b/graph_net/analysis_util.py @@ -1,5 +1,6 @@ import os import json +import re import numpy as np from scipy.stats import gmean from collections import OrderedDict, defaultdict @@ -84,6 +85,182 @@ def load_json_file(filepath: str) -> dict: return {} +def parse_logs_to_data(log_file: str) -> list: + """ + Parse a structured log file generated by the benchmark script and + return a list of data dictionaries (one per model-compiler run). + + This function directly parses log files without generating intermediate JSON files. + It automatically handles both Paddle (with subgraph) and PyTorch (without subgraph) samples. + + Args: + log_file: Path to the benchmark log file + + Returns: + List of data dictionaries, each containing configuration, correctness, + performance, and result information for a single model-compiler run. + """ + try: + with open(log_file, "r", encoding="utf-8") as f: + lines = f.readlines() + except FileNotFoundError: + print(f"Error: Log file not found at '{log_file}'") + return [] + except Exception as e: + print(f"Error reading log file: {e}") + return [] + + # Dictionary to hold parsed data for all runs + all_runs_data = {} + current_run_key = None + + # Define regex patterns for each type of log line + patterns = { + "processing": re.compile(r"\[Processing\] (.+)"), + "config": re.compile(r"\[Config\] (\S+): (.+)"), + "performance": re.compile(r"\[Performance\]\[(\w+)\]: (.+)"), + "datatype": re.compile(r"\[Datatype\]\[(\w+)\]: (.+)"), + "correctness": re.compile(r"\[Correctness\](\[.+\]): (.+)"), + "result_status": re.compile(r"\[Result\] status: (.+)"), + "failure": re.compile(r"\[Fail due to (.+)\.\]"), + "speedup": re.compile(r"\[Speedup\]\[(\w+)\]: (.+)"), + } + + for i, line in enumerate(lines): + # Check for the start of a new model run + processing_match = patterns["processing"].search(line) + if processing_match: + current_run_key = processing_match.group(1).strip() + # Initialize a nested dictionary structure for this new run + all_runs_data[current_run_key] = { + "configuration": {}, + "correctness": {}, + "performance": { + "eager": {}, + "compiled": {}, + "datatype": {}, + "speedup": {}, + }, + "result": { + "status": "unknown", + }, + } + continue + + # If we haven't identified a run yet, skip the line + if not current_run_key: + continue + + # Get the data dictionary for the current run + data = all_runs_data[current_run_key] + + # Try to match other patterns + config_match = patterns["config"].search(line) + if config_match: + key, value = config_match.groups() + data["configuration"][key.strip()] = value.strip() + continue + + performance_match = patterns["performance"].search(line) + if performance_match: + key, value_str = performance_match.groups() + # The performance value is a JSON string, so we load it + data["performance"][key.strip()] = json.loads(value_str) + continue + + datatype_match = patterns["datatype"].search(line) + if datatype_match: + key, value_str = datatype_match.groups() + # The datatype value is a space-separated string + data["performance"]["datatype"][key.strip()] = value_str.strip().split() + continue + + correctness_match = patterns["correctness"].search(line) + if correctness_match: + key, value_str = correctness_match.groups() + values = [] + for v in value_str.strip().split(): + try: + # Try to convert to int if it's a whole number, else float + values.append(int(v) if "." not in v else float(v)) + except ValueError: + # Handle non-numeric values like 'nan' + values.append(float(v)) + data["correctness"][key.strip()] = values + continue + + # Look for the status, and if it's "failed", look ahead to the next line. + result_status_match = patterns["result_status"].search(line) + if result_status_match: + status = result_status_match.group(1).strip() + data["result"]["status"] = status + if status == "failed" and (i + 1) < len(lines): + error_reason_match = patterns["failure"].search(lines[i + 1]) + if error_reason_match: + reason = error_reason_match.group(1).lower() + if "eager" in reason: + data["performance"]["failure"] = "eager" + data["result"]["status"] = "eager_fail" + elif "compiled" in reason: + data["performance"]["failure"] = "compiled" + data["result"]["status"] = "compile_fail" + else: + data["performance"]["failure"] = "other" + data["result"]["status"] = "runtime_fail" + continue + + speedup_match = patterns["speedup"].search(line) + if speedup_match: + key, value_str = speedup_match.groups() + data["performance"]["speedup"][key.strip()] = float(value_str) + continue + + # After parsing all lines, process the results + if not all_runs_data: + print("No processable log entries found in the file.") + return [] + + samples = [] + for run_key, data in all_runs_data.items(): + try: + # Build result field with status and speedup (for compatibility with log2json output format) + if data["result"]["status"] == "success": + speedup_data = {} + if "e2e" in data["performance"]["speedup"]: + e2e_value = data["performance"]["speedup"]["e2e"] + speedup_data["e2e"] = {"mean": e2e_value} + if "gpu" in data["performance"]["speedup"]: + gpu_value = data["performance"]["speedup"]["gpu"] + speedup_data["gpu"] = {"mean": gpu_value} + if speedup_data: + data["result"]["speedup"] = speedup_data + + # Ensure performance.speedup.e2e is a direct value (not nested dict) + # This is required by calculate_s_scores which uses performance_data.get("speedup", {}).get("e2e") + if "speedup" in data["performance"]: + speedup_dict = data["performance"]["speedup"] + if "e2e" in speedup_dict: + e2e_val = speedup_dict["e2e"] + if isinstance(e2e_val, dict) and "mean" in e2e_val: + speedup_dict["e2e"] = e2e_val["mean"] + if "gpu" in speedup_dict: + gpu_val = speedup_dict["gpu"] + if isinstance(gpu_val, dict) and "mean" in gpu_val: + speedup_dict["gpu"] = gpu_val["mean"] + + samples.append(data) + + except KeyError as e: + print(f"Warning: Could not process run '{run_key}' due to missing key: {e}") + except Exception as e: + print( + f"Warning: An unexpected error occurred while processing run '{run_key}': {e}" + ) + + print(f"Successfully parsed {len(samples)} samples from log file: {log_file}") + return samples + + def load_one_folder(folder_path: str) -> list: """ Traverse all .json files in a *single* folder and load all raw data. @@ -107,13 +284,35 @@ def load_one_folder(folder_path: str) -> list: def scan_all_folders(benchmark_path: str) -> dict: """ - Unified entry point: - - If there are .json files directly under benchmark_path → treat them as a single curve (curve name is the directory name). + Unified entry point that supports both log files and JSON directories: + - If benchmark_path is a log file → parse it directly and return data as a single curve. + - If benchmark_path is a directory with .json files directly under it → treat them as a single curve. - Otherwise, fallback to the old logic where subdirectories represent curves. Returns dict[folder_name] -> list_of_samples """ + # Check if the path is a log file + if os.path.isfile(benchmark_path): + print(f"Detected log file: '{benchmark_path}'") + samples = parse_logs_to_data(benchmark_path) + if samples: + # Use the log file name (without extension) as the curve name + folder_name = ( + os.path.splitext(os.path.basename(benchmark_path))[0] or "benchmark" + ) + print( + f" - Parsed log file → 1 curve '{folder_name}' " + f"with {len(samples)} samples." + ) + return {folder_name: samples} + else: + print(f" - No valid data found in log file.") + return {} + + # Check if it's a directory if not os.path.isdir(benchmark_path): - print(f"Error: Provided path '{benchmark_path}' is not a valid directory.") + print( + f"Error: Provided path '{benchmark_path}' is neither a valid file nor directory." + ) return {} print(f"Scanning '{benchmark_path}' ...") diff --git a/graph_net/plot_ESt.py b/graph_net/plot_ESt.py index 08562444b..38482688c 100644 --- a/graph_net/plot_ESt.py +++ b/graph_net/plot_ESt.py @@ -110,7 +110,7 @@ def main(): "--benchmark-path", type=str, required=True, - help="Path to the directory containing benchmark JSON files or sub-folders.", + help="Path to the benchmark log file or directory containing benchmark JSON files or sub-folders.", ) parser.add_argument( "--output-dir", diff --git a/graph_net/plot_St.py b/graph_net/plot_St.py index 92f7fa219..6817bacef 100644 --- a/graph_net/plot_St.py +++ b/graph_net/plot_St.py @@ -73,7 +73,7 @@ def main(): "--benchmark-path", type=str, required=True, - help="Path to the directory containing benchmark JSON files or sub-folders.", + help="Path to the benchmark log file or directory containing benchmark JSON files or sub-folders.", ) parser.add_argument( "--output-dir", diff --git a/graph_net/test/chain_naive_graph_decomposer_test.sh b/graph_net/test/chain_naive_graph_decomposer_test.sh index acdfd3f77..10ece10e1 100644 --- a/graph_net/test/chain_naive_graph_decomposer_test.sh +++ b/graph_net/test/chain_naive_graph_decomposer_test.sh @@ -20,4 +20,4 @@ EOF EXTRACTOR_CONFIG=$(echo $extractor_config_json_str | base64 -w 0) mkdir -p /tmp/naive_decompose_workspace -python3 -m graph_net.torch.single_device_runner --model-path $GRAPH_NET_ROOT/../samples/$MODEL_PATH_IN_SAMPLES --enable-extract True --extract-name resnet18 --dump-graph-hash-key --extractor-config=$EXTRACTOR_CONFIG +python3 -m graph_net.torch.single_device_runner --model-path $GRAPH_NET_ROOT/../samples/$MODEL_PATH_IN_SAMPLES --enable-extract True --extract-name resnet18 --dump-graph-hash-key --extractor-config=$EXTRACTOR_CONFIG \ No newline at end of file diff --git a/graph_net/test/decomposer_validator_test.sh b/graph_net/test/decomposer_validator_test.sh new file mode 100644 index 000000000..b1d3b01d6 --- /dev/null +++ b/graph_net/test/decomposer_validator_test.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +GRAPH_NET_ROOT=$(python3 -c "import graph_net; import os; print(os.path.dirname(graph_net.__file__))") + +if [ -z "$GRAPH_NET_DECOMPOSE_PATH" ]; then + GRAPH_NET_DECOMPOSE_PATH="$(pwd)/graphnet_decompose" +fi + +MODEL_PATH_IN_SAMPLES=/timm/resnet18 +MODEL_NAME=$(basename "$MODEL_PATH_IN_SAMPLES") +OUTPUT_DIR="${GRAPH_NET_DECOMPOSE_PATH:-$(pwd)}" +cp -r "$GRAPH_NET_ROOT/../samples/$MODEL_PATH_IN_SAMPLES" "$OUTPUT_DIR/" + +extractor_config_json_str=$(cat < "$FILE_PATH/log.log" 2>&1 + +python -m graph_net.log2json \ + --log-file "$FILE_PATH/log.log" \ + --output-dir "$FILE_PATH/JSON_results/" + +python -m graph_net.plot_ESt \ + --benchmark-path "$FILE_PATH/JSON_results/" \ + --output-dir "$FILE_PATH" + +echo "==================================================" +echo "Results saved in: $FILE_PATH/ES_result.png" +echo "" +echo "IMPORTANT: Please verify if the curve in ES_result.png is a straight line" +echo "If the curve is NOT a straight line, please check the log file: $FILE_PATH/log.log" +echo "==================================================" \ No newline at end of file diff --git a/graph_net/test/naive_graph_decomposer_test.sh b/graph_net/test/naive_graph_decomposer_test.sh index 2d333266b..f158544e4 100644 --- a/graph_net/test/naive_graph_decomposer_test.sh +++ b/graph_net/test/naive_graph_decomposer_test.sh @@ -21,4 +21,4 @@ EOF EXTRACTOR_CONFIG=$(echo $extractor_config_json_str | base64 -w 0) mkdir -p /tmp/naive_decompose_workspace -python3 -m graph_net.torch.single_device_runner --model-path $GRAPH_NET_ROOT/../samples/$MODEL_PATH_IN_SAMPLES --enable-extract True --extract-name resnet18 --dump-graph-hash-key --extractor-config=$EXTRACTOR_CONFIG +python3 -m graph_net.torch.single_device_runner --model-path $GRAPH_NET_ROOT/../samples/$MODEL_PATH_IN_SAMPLES --enable-extract True --extract-name resnet18 --dump-graph-hash-key --extractor-config=$EXTRACTOR_CONFIG \ No newline at end of file diff --git a/graph_net/torch/backend/range_decomposer_validator_backend.py b/graph_net/torch/backend/range_decomposer_validator_backend.py new file mode 100644 index 000000000..b1f71cdc7 --- /dev/null +++ b/graph_net/torch/backend/range_decomposer_validator_backend.py @@ -0,0 +1,76 @@ +import torch +import torch.nn as nn +import os +import sys +import inspect +import importlib.util +import itertools +from typing import List, Tuple, Dict, Any, Callable + + +class ComposedModel(nn.Module): + def __init__(self, subgraph: List[nn.Module]): + super().__init__() + self.subgraphs = nn.ModuleList(subgraph) + + def forward(self, **kwargs): + subgraph_intput = { + key.replace("L", "l_l", 1): value + for key, value in kwargs.items() + if key.startswith("L") + } + + output = None + for subgraph in self.subgraphs: + if output is None: + output = subgraph(**subgraph_intput) + else: + output = subgraph(*output) + + return output + + +class RangeDecomposerValidatorBackend: + def _load_model_instance(self, path: str, device: str) -> torch.nn.Module: + class_name = "GraphModule" + model_file = os.path.join(path, "model.py") + + spec = importlib.util.spec_from_file_location(class_name, model_file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + ModelClass = getattr(module, class_name) + instance = ModelClass().to(device) + return instance + + def __call__(self, model: torch.nn.Module) -> torch.nn.Module: + model_file_path = model.__class__.__graph_net_file_path__ + model_dir = os.path.dirname(model_file_path) + decomposed_parent_dir = model_dir + "_decomposed" + subgraph_paths = [] + for name in sorted(os.listdir(decomposed_parent_dir)): + full_path = os.path.join(decomposed_parent_dir, name) + if os.path.isdir(full_path) and name[-1].isdigit(): + subgraph_paths.append(full_path) + + print( + f"[RangeDecomposerValidatorBackend] Found subgraphs: {[os.path.basename(p) for p in subgraph_paths]}" + ) + + device = model.__class__.__graph_net_device__ + subgraph_instances = [] + + for path in subgraph_paths: + instance = self._load_model_instance(path, device) + subgraph_instances.append(instance) + dir_name = os.path.basename(path) + print( + f"[RangeDecomposerValidatorBackend] Loaded and instantiated '{dir_name}'" + ) + + composed_model = ComposedModel(subgraph_instances) + return composed_model.eval() + + def synchronize(self): + if torch.cuda.is_available(): + torch.cuda.synchronize() diff --git a/graph_net/torch/backend/unstable_to_stable_backend.py b/graph_net/torch/backend/unstable_to_stable_backend.py index 4302eeaf2..100f77a28 100644 --- a/graph_net/torch/backend/unstable_to_stable_backend.py +++ b/graph_net/torch/backend/unstable_to_stable_backend.py @@ -154,18 +154,98 @@ def _impl_unstable_to_stable_special_logit(self, gm): # Recompile the graph gm.recompile() - return gm - # replace this line with modification code for task 116 (torch._C._linalg.linalg_vector_norm) + def _impl_unstable_to_stable_linalg_vector_norm(self, gm): + """ + Convert torch._C._linalg.linalg_vector_norm to torch.linalg.vector_norm + """ + # Update graph nodes: replace torch._C._linalg.linalg_vector_norm with torch.linalg.vector_norm + issue_nodes = ( + node + for node in gm.graph.nodes + if node.op == "call_function" + if hasattr(node.target, "__module__") + if node.target.__module__ == "torch._C._linalg" + if hasattr(node.target, "__name__") + if node.target.__name__ == "linalg_vector_norm" + ) + for node in issue_nodes: + node.target = torch.linalg.vector_norm + + # Recompile the graph + gm.recompile() + + return gm # replace this line with modification code for task 117 (torch._C._linalg.linalg_norm) - # replace this line with modification code for task 118 (torch._C._nn.softplus) + def _impl_unstable_to_stable_softplus(self, gm): + """ + Convert torch._C._nn.softplus to torch.nn.functional.softplus + """ + import torch.nn.functional as F + + issue_nodes = ( + node + for node in gm.graph.nodes + if node.op == "call_function" + if hasattr(node.target, "__module__") + if node.target.__module__ == "torch._C._nn" + if hasattr(node.target, "__name__") + if node.target.__name__ == "softplus" + ) + for node in issue_nodes: + node.target = F.softplus + + gm.recompile() + return gm + + def _impl_unstable_to_stable_one_hot(self, gm): + """ + Convert torch._C._nn.one_hot to torch.nn.functional.one_hot + """ + import torch.nn.functional as F + + issue_nodes = ( + node + for node in gm.graph.nodes + if node.op == "call_function" + if hasattr(node.target, "__module__") + if node.target.__module__ == "torch._C._nn" + if hasattr(node.target, "__name__") + if node.target.__name__ == "one_hot" + ) + for node in issue_nodes: + node.target = F.one_hot + + # Recompile the graph + gm.recompile() - # replace this line with modification code for task 119 (torch._C._nn.one_hot) + return gm - # replace this line with modification code for task 121 (torch._C._set_grad_enabled) + def _impl_unstable_to_stable_set_grad_enabled(self, gm): + """ + Convert torch._C._set_grad_enabled and torch._C.set_grad_enabled to torch.set_grad_enabled + """ + + def replace_in_graph(graph_mod): + for node in graph_mod.graph.nodes: + if node.op == "call_function": + if "set_grad_enabled" in str(node.target): + node.target = torch.set_grad_enabled + graph_mod.recompile() + + modules = [gm] + modules += [ + m + for _, m in gm.named_modules() + if isinstance(m, torch.fx.GraphModule) and m is not gm + ] + for m in modules: + replace_in_graph(m) + + return gm # replace this line with modification code for task 122 (torch._C._log_api_usage_once) diff --git a/graph_net/torch/fx_graph_serialize_util.py b/graph_net/torch/fx_graph_serialize_util.py index eb50d7d4e..2ad78a4c0 100644 --- a/graph_net/torch/fx_graph_serialize_util.py +++ b/graph_net/torch/fx_graph_serialize_util.py @@ -139,11 +139,12 @@ def serialize_graph_module_to_str(gm: torch.fx.GraphModule) -> str: (r"torch\._C\._fft\.fft_rfft\(", "torch.fft.rfft("), (r"torch\._C\._fft\.fft_fftn\(", "torch.fft.fftn("), (r"torch\._C\._special\.special_logit\(", "torch.special.logit("), - # replace this line with modification code for task 116 (torch._C._linalg.linalg_vector_norm) + (r"torch\._C\._linalg\.linalg_vector_norm\(", "torch.linalg.vector_norm("), # replace this line with modification code for task 117 (torch._C._linalg.linalg_norm) - # replace this line with modification code for task 118 (torch._C._nn.softplus) - # replace this line with modification code for task 119 (torch._C._nn.one_hot) - # replace this line with modification code for task 121 (torch._C._set_grad_enabled) + (r"torch\._C\._nn\.softplus\(", "torch.nn.functional.softplus("), + (r"torch\._C\._nn\.one_hot\(", "torch.nn.functional.one_hot("), + (r"torch\._C\._set_grad_enabled\(", "torch.set_grad_enabled("), + (r"torch\._C\.set_grad_enabled\(", "torch.set_grad_enabled("), # replace this line with modification code for task 122 (torch._C._log_api_usage_once) # replace this line with modification code for task 123 (torch._C._nn.pad) # replace this line with modification code for task 125 (torch._C._nn.gelu) diff --git a/graph_net/torch/test_compiler.py b/graph_net/torch/test_compiler.py index f71359f1d..1095f24f5 100644 --- a/graph_net/torch/test_compiler.py +++ b/graph_net/torch/test_compiler.py @@ -23,7 +23,7 @@ from graph_net.torch.backend.blade_disc_backend import BladeDISCBackend from graph_net.torch.backend.nope_backend import NopeBackend from graph_net.torch.backend.unstable_to_stable_backend import UnstableToStableBackend -from todo_works.range_decomposer_validator.range_decomposer_validator import ( +from graph_net.torch.backend.range_decomposer_validator_backend import ( RangeDecomposerValidatorBackend, ) from graph_net.test_compiler_util import generate_allclose_configs @@ -69,6 +69,8 @@ def load_class_from_file( exec(compiled_code, module.__dict__) model_class = getattr(module, class_name, None) + setattr(model_class, "__graph_net_file_path__", file_path) + setattr(model_class, "__graph_net_device__", device) return model_class diff --git a/samples/transformers-auto-model/Qwen1.5-0.5B/graph_net.json b/samples/transformers-auto-model/Qwen1.5-0.5B/graph_net.json index 1dc8f04d6..040a66cde 100644 --- a/samples/transformers-auto-model/Qwen1.5-0.5B/graph_net.json +++ b/samples/transformers-auto-model/Qwen1.5-0.5B/graph_net.json @@ -4,5 +4,5 @@ "num_nodes_required": 1, "dynamic": false, "model_name": "Qwen/Qwen1.5-0.5B", - "heuristic_tag": "unknown" -} \ No newline at end of file + "heuristic_tag": "nlp" +} diff --git a/samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli/graph_net.json b/samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli/graph_net.json new file mode 100644 index 000000000..bb6c06e2f --- /dev/null +++ b/samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli/graph_net.json @@ -0,0 +1,7 @@ +{ + "framework": "torch", + "num_devices_required": 1, + "num_nodes_required": 1, + "source": "huggingface_hub", + "heuristic_tag": "nlp" +} diff --git a/todo_works/range_decomposer_validator/test/simple_CNN/input_meta.py b/samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli/input_meta.py similarity index 100% rename from todo_works/range_decomposer_validator/test/simple_CNN/input_meta.py rename to samples/transformers-auto-model/joeddav_xlm-roberta-large-xnli/input_meta.py diff --git a/todo_works/range_decomposer_validator/__init__.py b/todo_works/range_decomposer_validator/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/range_decomposer_validator.py b/todo_works/range_decomposer_validator/range_decomposer_validator.py deleted file mode 100644 index 18d062526..000000000 --- a/todo_works/range_decomposer_validator/range_decomposer_validator.py +++ /dev/null @@ -1,91 +0,0 @@ -import torch -import torch.nn as nn -import os -import sys -import inspect -import importlib.util -from typing import List, Dict - - -class ComposedModel(nn.Module): - def __init__(self, submodules: List[nn.Module]): - super().__init__() - self.submodules = nn.ModuleList(submodules) - self.submodule_param_names = [ - list(inspect.signature(sm.forward).parameters.keys()) - for sm in self.submodules - ] - - def forward(self, **kwargs): - current_args = kwargs - for i, (sm, param_names) in enumerate( - zip(self.submodules, self.submodule_param_names) - ): - # 准备当前子图的输入字典 - call_kwargs = {} - if i > 0: - # 对于后续子图,第一个参数是上一个子图的输出 - first_param_name = param_names[0] - call_kwargs[first_param_name] = current_args # current_args 此时是上一个子图的输出 - - # 从主输入字典中筛选出当前子图需要的权重参数 - for name in param_names: - if name in current_args: - call_kwargs[name] = current_args[name] - - outputs = sm(**call_kwargs) - # 假设每个子图只有一个输出,并且返回的是一个元组 - current_args = outputs[0] - - return (current_args,) - - -class RangeDecomposerValidatorBackend: - def _load_model_instance(self, path: str, device: str) -> torch.nn.Module: - class_name = "GraphModule" - model_file = os.path.join(path, "model.py") - - spec = importlib.util.spec_from_file_location(class_name, model_file) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - ModelClass = getattr(module, class_name) - instance = ModelClass().to(device) - return instance - - def __call__(self, model: torch.nn.Module) -> torch.nn.Module: - model_file_path = inspect.getfile( - model.__class__ - ) # e.g., /test/simple_CNN/model.py - model_dir = os.path.dirname(model_file_path) # e.g., /test/simple_CNN - - decomposed_parent_dir = ( - model_dir + "_decomposed" - ) # e.g., /test/simple_CNN_decomposed - subgraph_paths = [] - for name in sorted(os.listdir(decomposed_parent_dir)): - full_path = os.path.join(decomposed_parent_dir, name) - if os.path.isdir(full_path) and name.startswith("subgraph_"): - subgraph_paths.append(full_path) - - print( - f"[RangeDecomposerValidatorBackend] Found subgraphs: {[os.path.basename(p) for p in subgraph_paths]}" - ) - - submodule_instances = [] - device = next(model.parameters()).device # 从传入的model获取device信息 - - for path in subgraph_paths: - instance = self._load_model_instance(path, device) - submodule_instances.append(instance) - dir_name = os.path.basename(path) - print( - f"[RangeDecomposerValidatorBackend] Loaded and instantiated '{dir_name}'" - ) - - composed_model = ComposedModel(submodule_instances) - return composed_model.eval() - - def synchronize(self): - if torch.cuda.is_available(): - torch.cuda.synchronize() diff --git a/todo_works/range_decomposer_validator/test/simple_CNN/graph_hash.txt b/todo_works/range_decomposer_validator/test/simple_CNN/graph_hash.txt deleted file mode 100644 index 042fac7a8..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN/graph_hash.txt +++ /dev/null @@ -1 +0,0 @@ -c595a90bd71adf88efb78451fc9209bc31b574510f7b0dfae00c544b7cca97ca \ No newline at end of file diff --git a/todo_works/range_decomposer_validator/test/simple_CNN/graph_net.json b/todo_works/range_decomposer_validator/test/simple_CNN/graph_net.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN/input_tensor_constraints.py b/todo_works/range_decomposer_validator/test/simple_CNN/input_tensor_constraints.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN/model.py b/todo_works/range_decomposer_validator/test/simple_CNN/model.py deleted file mode 100644 index ca8cf4d43..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN/model.py +++ /dev/null @@ -1,92 +0,0 @@ -import torch - - -class GraphModule(torch.nn.Module): - def forward( - self, - L_x_: torch.Tensor, - L_self_modules_conv1_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_conv1_parameters_bias_: torch.nn.parameter.Parameter, - L_self_modules_conv2_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_conv2_parameters_bias_: torch.nn.parameter.Parameter, - L_self_modules_fc1_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_fc1_parameters_bias_: torch.nn.parameter.Parameter, - L_self_modules_fc2_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_fc2_parameters_bias_: torch.nn.parameter.Parameter, - ): - l_x_ = L_x_ - l_self_modules_conv1_parameters_weight_ = ( - L_self_modules_conv1_parameters_weight_ - ) - l_self_modules_conv1_parameters_bias_ = L_self_modules_conv1_parameters_bias_ - l_self_modules_conv2_parameters_weight_ = ( - L_self_modules_conv2_parameters_weight_ - ) - l_self_modules_conv2_parameters_bias_ = L_self_modules_conv2_parameters_bias_ - l_self_modules_fc1_parameters_weight_ = L_self_modules_fc1_parameters_weight_ - l_self_modules_fc1_parameters_bias_ = L_self_modules_fc1_parameters_bias_ - l_self_modules_fc2_parameters_weight_ = L_self_modules_fc2_parameters_weight_ - l_self_modules_fc2_parameters_bias_ = L_self_modules_fc2_parameters_bias_ - - # --- Subgraph 0 --- - # conv1 -> relu -> pool1 - input_1 = torch.conv2d( - l_x_, - l_self_modules_conv1_parameters_weight_, - l_self_modules_conv1_parameters_bias_, - (1, 1), # stride - (1, 1), # padding - (1, 1), # dilation - 1, # groups - ) - l_x_ = ( - l_self_modules_conv1_parameters_weight_ - ) = l_self_modules_conv1_parameters_bias_ = None - input_2 = torch.nn.functional.relu(input_1, inplace=True) - input_1 = None - input_3 = torch.nn.functional.max_pool2d(input_2, 2, 2, 0, 1, ceil_mode=False) - input_2 = None - - # --- Subgraph 1 --- - # conv2 -> relu -> pool2 - input_4 = torch.conv2d( - input_3, - l_self_modules_conv2_parameters_weight_, - l_self_modules_conv2_parameters_bias_, - (1, 1), - (1, 1), - (1, 1), - 1, - ) - input_3 = ( - l_self_modules_conv2_parameters_weight_ - ) = l_self_modules_conv2_parameters_bias_ = None - input_5 = torch.nn.functional.relu(input_4, inplace=True) - input_4 = None - input_6 = torch.nn.functional.max_pool2d(input_5, 2, 2, 0, 1, ceil_mode=False) - input_5 = None - - # --- Subgraph 2 --- - # flatten -> fc1 -> relu -> fc2 - input_7 = torch.flatten(input_6, 1) - input_6 = None - input_8 = torch._C._nn.linear( - input_7, - l_self_modules_fc1_parameters_weight_, - l_self_modules_fc1_parameters_bias_, - ) - input_7 = ( - l_self_modules_fc1_parameters_weight_ - ) = l_self_modules_fc1_parameters_bias_ = None - input_9 = torch.nn.functional.relu(input_8, inplace=True) - input_8 = None - input_10 = torch._C._nn.linear( - input_9, - l_self_modules_fc2_parameters_weight_, - l_self_modules_fc2_parameters_bias_, - ) - input_9 = ( - l_self_modules_fc2_parameters_weight_ - ) = l_self_modules_fc2_parameters_bias_ = None - - return (input_10,) diff --git a/todo_works/range_decomposer_validator/test/simple_CNN/weight_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN/weight_meta.py deleted file mode 100644 index 8c38d90e1..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN/weight_meta.py +++ /dev/null @@ -1,88 +0,0 @@ -class Program_weight_tensor_meta_L_x_: - name = "L_x_" - shape = [1, 1, 28, 28] # Batch size 1, 1 channel, 28x28 image - dtype = "torch.float32" - device = "cuda:0" - mean = 0.130 - std = 0.308 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv1_parameters_weight_: - name = "L_self_modules_conv1_parameters_weight_" - shape = [16, 1, 3, 3] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.001 - std = 0.108 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv1_parameters_bias_: - name = "L_self_modules_conv1_parameters_bias_" - shape = [16] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.1 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv2_parameters_weight_: - name = "L_self_modules_conv2_parameters_weight_" - shape = [32, 16, 3, 3] - dtype = "torch.float32" - device = "cuda:0" - mean = -0.002 - std = 0.055 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv2_parameters_bias_: - name = "L_self_modules_conv2_parameters_bias_" - shape = [32] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.05 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc1_parameters_weight_: - name = "L_self_modules_fc1_parameters_weight_" - shape = [128, 1568] # 1568 = 32 * 7 * 7 - dtype = "torch.float32" - device = "cuda:0" - mean = -0.000 - std = 0.025 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc1_parameters_bias_: - name = "L_self_modules_fc1_parameters_bias_" - shape = [128] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.02 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc2_parameters_weight_: - name = "L_self_modules_fc2_parameters_weight_" - shape = [10, 128] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.001 - std = 0.088 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc2_parameters_bias_: - name = "L_self_modules_fc2_parameters_bias_" - shape = [10] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.09 - data = None diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/graph_net.json b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/graph_net.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/input_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/input_meta.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/input_tensor_constraints.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/input_tensor_constraints.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/model.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/model.py deleted file mode 100644 index 10b0eda6f..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/model.py +++ /dev/null @@ -1,36 +0,0 @@ -import torch - - -class GraphModule(torch.nn.Module): - def forward( - self, - L_x_: torch.Tensor, - L_self_modules_conv1_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_conv1_parameters_bias_: torch.nn.parameter.Parameter, - ): - l_x_ = L_x_ - l_self_modules_conv1_parameters_weight_ = ( - L_self_modules_conv1_parameters_weight_ - ) - l_self_modules_conv1_parameters_bias_ = L_self_modules_conv1_parameters_bias_ - - # --- Subgraph 0 --- - # conv1 -> relu -> pool1 - input_1 = torch.conv2d( - l_x_, - l_self_modules_conv1_parameters_weight_, - l_self_modules_conv1_parameters_bias_, - (1, 1), # stride - (1, 1), # padding - (1, 1), # dilation - 1, # groups - ) - l_x_ = ( - l_self_modules_conv1_parameters_weight_ - ) = l_self_modules_conv1_parameters_bias_ = None - input_2 = torch.nn.functional.relu(input_1, inplace=True) - input_1 = None - input_3 = torch.nn.functional.max_pool2d(input_2, 2, 2, 0, 1, ceil_mode=False) - input_2 = None - - return (input_3,) diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/weight_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/weight_meta.py deleted file mode 100644 index 8ab978cf3..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_0/weight_meta.py +++ /dev/null @@ -1,28 +0,0 @@ -class Program_weight_tensor_meta_L_x_: - name = "L_x_" - shape = [1, 1, 28, 28] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.130 - std = 0.308 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv1_parameters_weight_: - name = "L_self_modules_conv1_parameters_weight_" - shape = [16, 1, 3, 3] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.001 - std = 0.108 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv1_parameters_bias_: - name = "L_self_modules_conv1_parameters_bias_" - shape = [16] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.1 - data = None diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/graph_net.json b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/graph_net.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/input_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/input_meta.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/input_tensor_constraints.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/input_tensor_constraints.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/model.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/model.py deleted file mode 100644 index 69149201e..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/model.py +++ /dev/null @@ -1,35 +0,0 @@ -import torch - - -class GraphModule(torch.nn.Module): - def forward( - self, - input_3: torch.Tensor, # Output of subgraph_0 - L_self_modules_conv2_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_conv2_parameters_bias_: torch.nn.parameter.Parameter, - ): - l_self_modules_conv2_parameters_weight_ = ( - L_self_modules_conv2_parameters_weight_ - ) - l_self_modules_conv2_parameters_bias_ = L_self_modules_conv2_parameters_bias_ - - # --- Subgraph 1 --- - # conv2 -> relu -> pool2 - input_4 = torch.conv2d( - input_3, - l_self_modules_conv2_parameters_weight_, - l_self_modules_conv2_parameters_bias_, - (1, 1), - (1, 1), - (1, 1), - 1, - ) - input_3 = ( - l_self_modules_conv2_parameters_weight_ - ) = l_self_modules_conv2_parameters_bias_ = None - input_5 = torch.nn.functional.relu(input_4, inplace=True) - input_4 = None - input_6 = torch.nn.functional.max_pool2d(input_5, 2, 2, 0, 1, ceil_mode=False) - input_5 = None - - return (input_6,) diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/weight_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/weight_meta.py deleted file mode 100644 index a4f9001dd..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_1/weight_meta.py +++ /dev/null @@ -1,29 +0,0 @@ -# 这是 subgraph_0 的输出,同时也是 subgraph_1 的输入 -class Program_weight_tensor_meta_input_3: - name = "input_3" - shape = [1, 16, 14, 14] # 28x28 经过一次 2x2 池化后变为 14x14 - dtype = "torch.float32" - device = "cuda:0" - mean = None - std = None - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv2_parameters_weight_: - name = "L_self_modules_conv2_parameters_weight_" - shape = [32, 16, 3, 3] - dtype = "torch.float32" - device = "cuda:0" - mean = -0.002 - std = 0.055 - data = None - - -class Program_weight_tensor_meta_L_self_modules_conv2_parameters_bias_: - name = "L_self_modules_conv2_parameters_bias_" - shape = [32] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.05 - data = None diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/graph_net.json b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/graph_net.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/input_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/input_meta.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/input_tensor_constraints.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/input_tensor_constraints.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/model.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/model.py deleted file mode 100644 index bad03c955..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/model.py +++ /dev/null @@ -1,41 +0,0 @@ -import torch - - -class GraphModule(torch.nn.Module): - def forward( - self, - input_6: torch.Tensor, # Output of subgraph_1 - L_self_modules_fc1_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_fc1_parameters_bias_: torch.nn.parameter.Parameter, - L_self_modules_fc2_parameters_weight_: torch.nn.parameter.Parameter, - L_self_modules_fc2_parameters_bias_: torch.nn.parameter.Parameter, - ): - l_self_modules_fc1_parameters_weight_ = L_self_modules_fc1_parameters_weight_ - l_self_modules_fc1_parameters_bias_ = L_self_modules_fc1_parameters_bias_ - l_self_modules_fc2_parameters_weight_ = L_self_modules_fc2_parameters_weight_ - l_self_modules_fc2_parameters_bias_ = L_self_modules_fc2_parameters_bias_ - - # --- Subgraph 2 --- - # flatten -> fc1 -> relu -> fc2 - input_7 = torch.flatten(input_6, 1) - input_6 = None - input_8 = torch._C._nn.linear( - input_7, - l_self_modules_fc1_parameters_weight_, - l_self_modules_fc1_parameters_bias_, - ) - input_7 = ( - l_self_modules_fc1_parameters_weight_ - ) = l_self_modules_fc1_parameters_bias_ = None - input_9 = torch.nn.functional.relu(input_8, inplace=True) - input_8 = None - input_10 = torch._C._nn.linear( - input_9, - l_self_modules_fc2_parameters_weight_, - l_self_modules_fc2_parameters_bias_, - ) - input_9 = ( - l_self_modules_fc2_parameters_weight_ - ) = l_self_modules_fc2_parameters_bias_ = None - - return (input_10,) diff --git a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/weight_meta.py b/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/weight_meta.py deleted file mode 100644 index 71ed88e14..000000000 --- a/todo_works/range_decomposer_validator/test/simple_CNN_decomposed/subgraph_2/weight_meta.py +++ /dev/null @@ -1,49 +0,0 @@ -# 这是 subgraph_1 的输出,同时也是 subgraph_2 的输入 -class Program_weight_tensor_meta_input_6: - name = "input_6" - shape = [1, 32, 7, 7] # 14x14 经过一次 2x2 池化后变为 7x7 - dtype = "torch.float32" - device = "cuda:0" - mean = None - std = None - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc1_parameters_weight_: - name = "L_self_modules_fc1_parameters_weight_" - shape = [128, 1568] # 1568 = 32 * 7 * 7 - dtype = "torch.float32" - device = "cuda:0" - mean = -0.000 - std = 0.025 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc1_parameters_bias_: - name = "L_self_modules_fc1_parameters_bias_" - shape = [128] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.02 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc2_parameters_weight_: - name = "L_self_modules_fc2_parameters_weight_" - shape = [10, 128] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.001 - std = 0.088 - data = None - - -class Program_weight_tensor_meta_L_self_modules_fc2_parameters_bias_: - name = "L_self_modules_fc2_parameters_bias_" - shape = [10] - dtype = "torch.float32" - device = "cuda:0" - mean = 0.0 - std = 0.09 - data = None diff --git a/tools/check_and_count_samples.py b/tools/check_and_count_samples.py new file mode 100644 index 000000000..0a82f3584 --- /dev/null +++ b/tools/check_and_count_samples.py @@ -0,0 +1,139 @@ +import os +import json + + +def check_completeness(samples_dir): + samples_missing_hash = [] + samples_missing_json = [] + samples_missing_meta = [] + samples_missing_model = [] + for root, dirs, files in os.walk(samples_dir): + model_path = root + if "shape_patches_" not in root and "model.py" in files: + if not os.path.exists(os.path.join(model_path, "graph_hash.txt")): + samples_missing_hash.append(model_path) + if not os.path.exists(os.path.join(model_path, "graph_net.json")): + samples_missing_json.append(model_path) + if not os.path.exists( + os.path.join(model_path, "input_meta.py") + ) or not os.path.exists(os.path.join(model_path, "weight_meta.py")): + samples_missing_meta.append(model_path) + if "graph_net.json" in files and "model.py" not in files: + samples_missing_model.append(model_path) + + all_samples_complete = ( + len(samples_missing_hash) == 0 + and len(samples_missing_json) == 0 + and len(samples_missing_meta) == 0 + and len(samples_missing_model) == 0 + ) + + if not all_samples_complete: + print(f"Check completeness result for {samples_dir}:") + print(f"1. {len(samples_missing_hash)} samples missing graph_hash.txt") + for model_path in samples_missing_hash: + print(f" - {model_path}") + + print(f"2. {len(samples_missing_json)} samples missing graph_net.json") + for model_path in samples_missing_json: + print(f" - {model_path}") + + print( + f"3. {len(samples_missing_meta)} samples missing input_meta.py or weight_meta.py" + ) + for model_path in samples_missing_meta: + print(f" - {model_path}") + + print(f"4. {len(samples_missing_model)} samples missing model.py") + for model_path in samples_missing_model: + print(f" - {model_path}") + + print() + + return all_samples_complete + + +def check_redandancy(samples_dir): + graph_hash2model_paths = {} + for root, dirs, files in os.walk(samples_dir): + if "graph_hash.txt" in files: + model_path = root + graph_hash_path = os.path.join(model_path, "graph_hash.txt") + graph_hash = open(graph_hash_path).read() + if graph_hash not in graph_hash2model_paths.keys(): + graph_hash2model_paths[graph_hash] = [model_path] + else: + graph_hash2model_paths[graph_hash].append(model_path) + + has_duplicates = False + print(f"Totally {len(graph_hash2model_paths)} unique graphs under {samples_dir}.") + for graph_hash, model_paths in graph_hash2model_paths.items(): + graph_hash2model_paths[graph_hash] = sorted(model_paths) + if len(model_paths) > 1: + has_duplicates = True + print(f"Redundant models detected for grap_hash {graph_hash}:") + for model_path in model_paths: + print(f" {model_path}") + return has_duplicates, graph_hash2model_paths + + +def count_samples(samples_dir, framework): + model_sources = os.listdir(samples_dir) + + graph_net_count = 0 + graph_net_dict = {} + model_names_set = set() + for source in model_sources: + source_dir = os.path.join(samples_dir, source) + if os.path.isdir(source_dir): + graph_net_dict[source] = 0 + for root, dirs, files in os.walk(source_dir): + if "graph_net.json" in files: + with open(os.path.join(root, "graph_net.json"), "r") as f: + data = json.load(f) + model_name = data.get("model_name", None) + if model_name is not None and model_name != "NO_VALID_MATCH_FOUND": + if model_name not in model_names_set: + model_names_set.add(model_name) + graph_net_count += 1 + graph_net_dict[source] += 1 + else: + graph_net_count += 1 + graph_net_dict[source] += 1 + + print(f"Number of {framework} samples: {graph_net_count}") + for name, number in graph_net_dict.items(): + print(f"- {name:24}: {number}") + print() + + +def main(): + filename = os.path.abspath(__file__) + root_dir = os.path.dirname(os.path.dirname(filename)) + + framework2dirname = { + "torch": "samples", + "paddle": "paddle_samples", + } + + all_samples_complete = True + for samples_dirname in framework2dirname.values(): + samples_dir = os.path.join(root_dir, samples_dirname) + all_samples_complete = all_samples_complete and check_completeness(samples_dir) + assert all_samples_complete, "Please fix the incompleted samples!" + + all_samples_has_duplicates = False + for samples_dirname in framework2dirname.values(): + samples_dir = os.path.join(root_dir, samples_dirname) + has_duplicates, graph_hash2model_paths = check_redandancy(samples_dir) + all_samples_has_duplicates = all_samples_has_duplicates or has_duplicates + print() + assert not all_samples_has_duplicates, "Please remove the redundant samples!" + + for framework in framework2dirname.keys(): + samples_dir = os.path.join(root_dir, framework2dirname[framework]) + count_samples(samples_dir, framework) + + +if __name__ == "__main__": + main() diff --git a/tools/ci/check_validate.sh b/tools/ci/check_validate.sh index 5c746fda7..e421b225f 100644 --- a/tools/ci/check_validate.sh +++ b/tools/ci/check_validate.sh @@ -41,7 +41,6 @@ function prepare_torch_env() { python3.10 -m pip install --pre torch --index-url https://download.pytorch.org/whl/nightly/cu126 > /dev/null [ $? -ne 0 ] && LOG "[FATAL] Install torch2.9.0 failed!" && exit -1 else - python3.10 ${GRAPH_NET_EXTRACT_WORKSPACE}/tools/count_sample.py LOG "[INFO] This pull request doesn't change any torch samples, skip the CI." fi } @@ -62,7 +61,6 @@ function prepare_paddle_env() { [ $? -ne 0 ] && LOG "[FATAL] Install paddlepaddle-develop failed!" && exit -1 python3.10 -c "import paddle; print('[PaddlePaddle Commit]', paddle.version.commit)" else - python3.10 ${GRAPH_NET_EXTRACT_WORKSPACE}/tools/count_sample.py LOG "[INFO] This pull request doesn't change any paddle samples, skip the CI." fi } @@ -165,7 +163,8 @@ function main() { check_validation_info=$(check_paddle_validation) check_validation_code=$? summary_problems $check_validation_code "$check_validation_info" - python3.10 ${GRAPH_NET_EXTRACT_WORKSPACE}/tools/count_sample.py + python3.10 ${GRAPH_NET_EXTRACT_WORKSPACE}/tools/check_and_count_samples.py >&2 + [ $? -ne 0 ] && LOG "[FATAL] Check completeness or redundancy failed!" && exit -1 LOG "[INFO] check_validation run success and no error!" } diff --git a/tools/count_sample.py b/tools/count_sample.py deleted file mode 100644 index 3addb4d5a..000000000 --- a/tools/count_sample.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -import json - - -filename = os.path.abspath(__file__) -root_dir = os.path.dirname(os.path.dirname(filename)) -framework2dirname = { - "torch": "samples", - "paddle": "paddle_samples", -} - -for framework in ["torch", "paddle"]: - samples_dir = os.path.join(root_dir, framework2dirname[framework]) - model_categories = os.listdir(samples_dir) - - graph_net_count = 0 - graph_net_dict = {} - model_names_set = set() - for category in model_categories: - category_dir = os.path.join(samples_dir, category) - if os.path.isdir(category_dir): - graph_net_dict[category] = 0 - for root, dirs, files in os.walk(category_dir): - if "graph_net.json" in files: - with open(os.path.join(root, "graph_net.json"), "r") as f: - data = json.load(f) - model_name = data.get("model_name", None) - if model_name is not None: - if model_name not in model_names_set: - model_names_set.add(model_name) - graph_net_count += 1 - graph_net_dict[category] += 1 - else: - graph_net_count += 1 - graph_net_dict[category] += 1 - - print(f"Number of {framework} samples: {graph_net_count}") - for name, number in graph_net_dict.items(): - print(f"- {name:24}: {number}") - print() From 81618f8ea1cb5bea4d0666e67c9ce9f53058dc09 Mon Sep 17 00:00:00 2001 From: Dayuxiaoshui <792179245@qq.com> Date: Thu, 13 Nov 2025 17:28:59 +0800 Subject: [PATCH 2/3] Refactor if nesting in analysis_util.py to reduce indentation levels - Use early returns and continue statements to reduce nesting - Optimize if nesting in extract_speedup_data_from_subdirs, parse_logs_to_data, and calculate_s_scores functions - Improve code readability and maintainability by flattening nested conditionals --- graph_net/analysis_util.py | 318 ++++++++++++++++++++++++------------- 1 file changed, 204 insertions(+), 114 deletions(-) diff --git a/graph_net/analysis_util.py b/graph_net/analysis_util.py index e4f192391..4070193d3 100644 --- a/graph_net/analysis_util.py +++ b/graph_net/analysis_util.py @@ -41,34 +41,32 @@ def extract_speedup_data_from_subdirs(benchmark_path: str) -> dict: # but os.walk is also robust for nested directories if needed in the future. for root, _, files in os.walk(current_dir_path): for file in files: - if file.endswith(".json"): - json_file = os.path.join(root, file) - try: - with open(json_file, "r") as f: - data = json.load(f) - performance = data.get("performance", {}) - if not performance: - continue - - speedup_data = performance.get("speedup") - if isinstance(speedup_data, dict): - # Prioritize 'e2e' speedup, fallback to 'gpu' - if "e2e" in speedup_data: - data_by_subdir[subdir_name].append( - speedup_data["e2e"] - ) - elif "gpu" in speedup_data: - data_by_subdir[subdir_name].append( - speedup_data["gpu"] - ) - elif isinstance(speedup_data, (float, int)): - data_by_subdir[subdir_name].append(speedup_data) - - except (json.JSONDecodeError, KeyError) as e: - print( - f"Warning: Failed to read or parse file -> {json_file}, Error: {e}" - ) - continue + if not file.endswith(".json"): + continue + + json_file = os.path.join(root, file) + try: + with open(json_file, "r") as f: + data = json.load(f) + performance = data.get("performance", {}) + if not performance: + continue + + speedup_data = performance.get("speedup") + if isinstance(speedup_data, dict): + # Prioritize 'e2e' speedup, fallback to 'gpu' + if "e2e" in speedup_data: + data_by_subdir[subdir_name].append(speedup_data["e2e"]) + elif "gpu" in speedup_data: + data_by_subdir[subdir_name].append(speedup_data["gpu"]) + elif isinstance(speedup_data, (float, int)): + data_by_subdir[subdir_name].append(speedup_data) + + except (json.JSONDecodeError, KeyError) as e: + print( + f"Warning: Failed to read or parse file -> {json_file}, Error: {e}" + ) + continue return data_by_subdir @@ -85,6 +83,72 @@ def load_json_file(filepath: str) -> dict: return {} +def detect_sample_error_code(log_text: str) -> str: + """ + Detect the error code for a single sample from log text. + + This function is used for bug subgraph detection. It analyzes log text + (which can be generated from a single sample) and returns an error code. + + Args: + log_text: Log text content (can be a string or list of lines) + + Returns: + Error code string. Possible values: + - "correct": Sample executed successfully + - "eager_fail": Eager model execution failed + - "compile_fail": Compiled model compilation failed + - "runtime_fail": Runtime error during execution + - "unknown": Unable to determine error type + """ + if isinstance(log_text, str): + lines = log_text.split("\n") + else: + lines = log_text + + # Define regex patterns for error detection + patterns = { + "result_status": re.compile(r"\[Result\] status: (.+)"), + "failure": re.compile(r"\[Fail due to (.+)\.\]"), + } + + # Error type mapping based on failure reason keywords + error_keywords = { + "eager": "eager_fail", + "compiled": "compile_fail", + } + + for i, line in enumerate(lines): + result_status_match = patterns["result_status"].search(line) + if not result_status_match: + continue + + status = result_status_match.group(1).strip() + if status == "success": + return "correct" + + if status != "failed": + continue + + # Check the next line for failure reason + if (i + 1) >= len(lines): + return "runtime_fail" + + error_reason_match = patterns["failure"].search(lines[i + 1]) + if not error_reason_match: + return "runtime_fail" + + reason = error_reason_match.group(1).lower() + # Check for specific error keywords + for keyword, error_code in error_keywords.items(): + if keyword in reason: + return error_code + + return "runtime_fail" + + return "unknown" + + def parse_logs_to_data(log_file: str) -> list: """ Parse a structured log file generated by the benchmark script and @@ -191,30 +255,34 @@ def parse_logs_to_data(log_file: str) -> list: # Look for the status, and if it's "failed", look ahead to the next line. result_status_match = patterns["result_status"].search(line) - if result_status_match: - status = result_status_match.group(1).strip() - data["result"]["status"] = status - if status == "failed" and (i + 1) < len(lines): - error_reason_match = patterns["failure"].search(lines[i + 1]) - if error_reason_match: - reason = error_reason_match.group(1).lower() - if "eager" in reason: - data["performance"]["failure"] = "eager" - data["result"]["status"] = "eager_fail" - elif "compiled" in reason: - data["performance"]["failure"] = "compiled" - data["result"]["status"] = "compile_fail" - else: - data["performance"]["failure"] = "other" - data["result"]["status"] = "runtime_fail" + if not result_status_match: + speedup_match = patterns["speedup"].search(line) + if speedup_match: + key, value_str = speedup_match.groups() + data["performance"]["speedup"][key.strip()] = float(value_str) + continue + + status = result_status_match.group(1).strip() + data["result"]["status"] = status + if status != "failed" or (i + 1) >= len(lines): continue - speedup_match = patterns["speedup"].search(line) - if speedup_match: - key, value_str = speedup_match.groups() - data["performance"]["speedup"][key.strip()] = float(value_str) + error_reason_match = patterns["failure"].search(lines[i + 1]) + if not error_reason_match: continue + reason = error_reason_match.group(1).lower() + if "eager" in reason: + data["performance"]["failure"] = "eager" + data["result"]["status"] = "eager_fail" + elif "compiled" in reason: + data["performance"]["failure"] = "compiled" + data["result"]["status"] = "compile_fail" + else: + data["performance"]["failure"] = "other" + data["result"]["status"] = "runtime_fail" + continue + # After parsing all lines, process the results if not all_runs_data: print("No processable log entries found in the file.") @@ -237,16 +305,19 @@ def parse_logs_to_data(log_file: str) -> list: # Ensure performance.speedup.e2e is a direct value (not nested dict) # This is required by calculate_s_scores which uses performance_data.get("speedup", {}).get("e2e") - if "speedup" in data["performance"]: - speedup_dict = data["performance"]["speedup"] - if "e2e" in speedup_dict: - e2e_val = speedup_dict["e2e"] - if isinstance(e2e_val, dict) and "mean" in e2e_val: - speedup_dict["e2e"] = e2e_val["mean"] - if "gpu" in speedup_dict: - gpu_val = speedup_dict["gpu"] - if isinstance(gpu_val, dict) and "mean" in gpu_val: - speedup_dict["gpu"] = gpu_val["mean"] + speedup_dict = data["performance"].get("speedup") + if not speedup_dict: + samples.append(data) + continue + + if "e2e" in speedup_dict: + e2e_val = speedup_dict["e2e"] + if isinstance(e2e_val, dict) and "mean" in e2e_val: + speedup_dict["e2e"] = e2e_val["mean"] + if "gpu" in speedup_dict: + gpu_val = speedup_dict["gpu"] + if isinstance(gpu_val, dict) and "mean" in gpu_val: + speedup_dict["gpu"] = gpu_val["mean"] samples.append(data) @@ -261,53 +332,31 @@ def parse_logs_to_data(log_file: str) -> list: return samples -def load_one_folder(folder_path: str) -> list: - """ - Traverse all .json files in a *single* folder and load all raw data. - Returns a list of raw data dictionaries. - """ - if not os.path.isdir(folder_path): - return [] - - folder_name = os.path.basename(folder_path) - samples = [] - print(f" - Loading JSON files from folder: {folder_path}") - - for filename in os.listdir(folder_path): - if filename.endswith(".json"): - filepath = os.path.join(folder_path, filename) - data = load_json_file(filepath) - if data: - samples.append(data) - return samples - - def scan_all_folders(benchmark_path: str) -> dict: """ - Unified entry point that supports both log files and JSON directories: + Unified entry point that supports log files and directories: - If benchmark_path is a log file → parse it directly and return data as a single curve. - - If benchmark_path is a directory with .json files directly under it → treat them as a single curve. - - Otherwise, fallback to the old logic where subdirectories represent curves. - Returns dict[folder_name] -> list_of_samples + - If benchmark_path is a directory → scan for .log files, each log file becomes a curve. + If no .log files found in root, scan subdirectories (each subdirectory becomes a curve). + Returns dict[curve_name] -> list_of_samples """ - # Check if the path is a log file + # Handle single log file if os.path.isfile(benchmark_path): print(f"Detected log file: '{benchmark_path}'") samples = parse_logs_to_data(benchmark_path) - if samples: - # Use the log file name (without extension) as the curve name - folder_name = ( - os.path.splitext(os.path.basename(benchmark_path))[0] or "benchmark" - ) - print( - f" - Parsed log file → 1 curve '{folder_name}' " - f"with {len(samples)} samples." - ) - return {folder_name: samples} - else: + if not samples: print(f" - No valid data found in log file.") return {} + folder_name = ( + os.path.splitext(os.path.basename(benchmark_path))[0] or "benchmark" + ) + print( + f" - Parsed log file → 1 curve '{folder_name}' " + f"with {len(samples)} samples." + ) + return {folder_name: samples} + # Check if it's a directory if not os.path.isdir(benchmark_path): print( @@ -317,27 +366,66 @@ def scan_all_folders(benchmark_path: str) -> dict: print(f"Scanning '{benchmark_path}' ...") - # Try flat structure, directly read JSON - flat_samples = load_one_folder(benchmark_path) - if flat_samples: # ≥1 JSON loaded successfully - folder_name = os.path.basename(benchmark_path) or "benchmark" - print( - f" - Detected flat structure → 1 curve '{folder_name}' " - f"with {len(flat_samples)} samples." - ) - return {folder_name: flat_samples} + # Find .log files in the root directory + log_files = [ + f + for f in os.listdir(benchmark_path) + if os.path.isfile(os.path.join(benchmark_path, f)) and f.endswith(".log") + ] + + # Process root-level log files + if log_files: + all_results = {} + print(f" - Found {len(log_files)} log file(s) → each becomes a curve.") + for log_file in sorted(log_files): + log_file_path = os.path.join(benchmark_path, log_file) + samples = parse_logs_to_data(log_file_path) + if not samples: + continue + + curve_name = os.path.splitext(log_file)[0] or "benchmark" + all_results[curve_name] = samples + print(f" - Curve '{curve_name}': {len(samples)} samples.") + + if not all_results: + print(" - No valid data found in any log file.") + return {} + + print(f"Total curves loaded: {len(all_results)}") + return all_results - # Fall back to subdirectories as curves logic + # Fall back to subdirectories all_results = {} - print(" - No JSON files found at top level → scanning sub-folders.") + print(" - No log files found in root → scanning sub-folders.") for entry in os.listdir(benchmark_path): folder_full_path = os.path.join(benchmark_path, entry) - if os.path.isdir(folder_full_path): - samples = load_one_folder(folder_full_path) - if samples: - all_results[entry] = samples - print(f" - Folder '{entry}' loaded {len(samples)} samples.") - print(f"Total folders loaded: {len(all_results)}") + if not os.path.isdir(folder_full_path): + continue + + # Find log files in subdirectory + sub_log_files = [ + f + for f in os.listdir(folder_full_path) + if os.path.isfile(os.path.join(folder_full_path, f)) and f.endswith(".log") + ] + if not sub_log_files: + continue + + # Parse and combine log files from subdirectory + combined_samples = [] + for log_file in sub_log_files: + log_file_path = os.path.join(folder_full_path, log_file) + samples = parse_logs_to_data(log_file_path) + combined_samples.extend(samples) + + if not combined_samples: + continue + + all_results[entry] = combined_samples + print(f" - Folder '{entry}': {len(combined_samples)} samples from log files.") + + if all_results: + print(f"Total folders loaded: {len(all_results)}") return all_results @@ -478,7 +566,10 @@ def print_stat_info( # Determine the true state of the current sample (for statistics and S curve) is_correct = False - if fail_type is None: + if fail_type is not None: + # Already has a failure type, skip correctness check + pass + else: datatype_data = performance_data.get("datatype", {}) eager_dtypes = datatype_data.get("eager", []) compiled_dtypes = datatype_data.get("compiled", []) @@ -526,7 +617,6 @@ def print_stat_info( rectified_speedups.append(regularized_speedup) # ES(t) calculation: based on state change - rec_speedup_fake_degrad = 0 if t_key < 1: if fail_type is not None or speedup is None: rec_speedup_fake_degrad = fpdb From fe57cf8c77e5a848db04e05d4f498c1ab9a01dd3 Mon Sep 17 00:00:00 2001 From: Dayuxiaoshui <792179245@qq.com> Date: Thu, 13 Nov 2025 18:19:16 +0800 Subject: [PATCH 3/3] Simplify scan_all_folders and add .txt file support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove subdirectory scanning logic, only handle two cases: (1) single log file → one curve (2) directory with multiple log files → each log file becomes a curve - Add support for .txt files in addition to .log files - Further simplify code by extracting duplicate logic and removing unnecessary code - Move speedup pattern matching earlier in parse_logs_to_data for better flow - Use loops to handle e2e/gpu processing to eliminate code duplication --- graph_net/analysis_util.py | 133 +++++++++++++------------------------ 1 file changed, 48 insertions(+), 85 deletions(-) diff --git a/graph_net/analysis_util.py b/graph_net/analysis_util.py index 4070193d3..d5150f8c8 100644 --- a/graph_net/analysis_util.py +++ b/graph_net/analysis_util.py @@ -253,13 +253,16 @@ def parse_logs_to_data(log_file: str) -> list: data["correctness"][key.strip()] = values continue + # Check for speedup + speedup_match = patterns["speedup"].search(line) + if speedup_match: + key, value_str = speedup_match.groups() + data["performance"]["speedup"][key.strip()] = float(value_str) + continue + # Look for the status, and if it's "failed", look ahead to the next line. result_status_match = patterns["result_status"].search(line) if not result_status_match: - speedup_match = patterns["speedup"].search(line) - if speedup_match: - key, value_str = speedup_match.groups() - data["performance"]["speedup"][key.strip()] = float(value_str) continue status = result_status_match.group(1).strip() @@ -291,33 +294,24 @@ def parse_logs_to_data(log_file: str) -> list: samples = [] for run_key, data in all_runs_data.items(): try: + speedup_dict = data["performance"].get("speedup", {}) + # Build result field with status and speedup (for compatibility with log2json output format) - if data["result"]["status"] == "success": + if data["result"]["status"] == "success" and speedup_dict: speedup_data = {} - if "e2e" in data["performance"]["speedup"]: - e2e_value = data["performance"]["speedup"]["e2e"] - speedup_data["e2e"] = {"mean": e2e_value} - if "gpu" in data["performance"]["speedup"]: - gpu_value = data["performance"]["speedup"]["gpu"] - speedup_data["gpu"] = {"mean": gpu_value} + for key in ["e2e", "gpu"]: + if key in speedup_dict: + speedup_data[key] = {"mean": speedup_dict[key]} if speedup_data: data["result"]["speedup"] = speedup_data - # Ensure performance.speedup.e2e is a direct value (not nested dict) + # Ensure performance.speedup.e2e/gpu are direct values (not nested dict) # This is required by calculate_s_scores which uses performance_data.get("speedup", {}).get("e2e") - speedup_dict = data["performance"].get("speedup") - if not speedup_dict: - samples.append(data) - continue - - if "e2e" in speedup_dict: - e2e_val = speedup_dict["e2e"] - if isinstance(e2e_val, dict) and "mean" in e2e_val: - speedup_dict["e2e"] = e2e_val["mean"] - if "gpu" in speedup_dict: - gpu_val = speedup_dict["gpu"] - if isinstance(gpu_val, dict) and "mean" in gpu_val: - speedup_dict["gpu"] = gpu_val["mean"] + for key in ["e2e", "gpu"]: + if key in speedup_dict: + val = speedup_dict[key] + if isinstance(val, dict) and "mean" in val: + speedup_dict[key] = val["mean"] samples.append(data) @@ -335,9 +329,9 @@ def parse_logs_to_data(log_file: str) -> list: def scan_all_folders(benchmark_path: str) -> dict: """ Unified entry point that supports log files and directories: - - If benchmark_path is a log file → parse it directly and return data as a single curve. - - If benchmark_path is a directory → scan for .log files, each log file becomes a curve. - If no .log files found in root, scan subdirectories (each subdirectory becomes a curve). + - If benchmark_path is a log file (.log or .txt) → parse it directly and return data as a single curve. + - If benchmark_path is a directory → scan for .log and .txt files in the directory, + each log file becomes a curve. Returns dict[curve_name] -> list_of_samples """ # Handle single log file @@ -366,66 +360,38 @@ def scan_all_folders(benchmark_path: str) -> dict: print(f"Scanning '{benchmark_path}' ...") - # Find .log files in the root directory - log_files = [ - f - for f in os.listdir(benchmark_path) - if os.path.isfile(os.path.join(benchmark_path, f)) and f.endswith(".log") - ] - - # Process root-level log files - if log_files: - all_results = {} - print(f" - Found {len(log_files)} log file(s) → each becomes a curve.") - for log_file in sorted(log_files): - log_file_path = os.path.join(benchmark_path, log_file) - samples = parse_logs_to_data(log_file_path) - if not samples: - continue - - curve_name = os.path.splitext(log_file)[0] or "benchmark" - all_results[curve_name] = samples - print(f" - Curve '{curve_name}': {len(samples)} samples.") - - if not all_results: - print(" - No valid data found in any log file.") - return {} - - print(f"Total curves loaded: {len(all_results)}") - return all_results - - # Fall back to subdirectories - all_results = {} - print(" - No log files found in root → scanning sub-folders.") - for entry in os.listdir(benchmark_path): - folder_full_path = os.path.join(benchmark_path, entry) - if not os.path.isdir(folder_full_path): - continue - - # Find log files in subdirectory - sub_log_files = [ + # Find .log and .txt files in the directory + log_files = sorted( + [ f - for f in os.listdir(folder_full_path) - if os.path.isfile(os.path.join(folder_full_path, f)) and f.endswith(".log") + for f in os.listdir(benchmark_path) + if os.path.isfile(os.path.join(benchmark_path, f)) + and f.endswith((".log", ".txt")) ] - if not sub_log_files: - continue + ) - # Parse and combine log files from subdirectory - combined_samples = [] - for log_file in sub_log_files: - log_file_path = os.path.join(folder_full_path, log_file) - samples = parse_logs_to_data(log_file_path) - combined_samples.extend(samples) + if not log_files: + print(" - No log files (.log or .txt) found in directory.") + return {} - if not combined_samples: + # Process log files, each becomes a curve + all_results = {} + print(f" - Found {len(log_files)} log file(s) → each becomes a curve.") + for log_file in log_files: + log_file_path = os.path.join(benchmark_path, log_file) + samples = parse_logs_to_data(log_file_path) + if not samples: continue - all_results[entry] = combined_samples - print(f" - Folder '{entry}': {len(combined_samples)} samples from log files.") + curve_name = os.path.splitext(log_file)[0] or "benchmark" + all_results[curve_name] = samples + print(f" - Curve '{curve_name}': {len(samples)} samples.") + + if not all_results: + print(" - No valid data found in any log file.") + return {} - if all_results: - print(f"Total folders loaded: {len(all_results)}") + print(f"Total curves loaded: {len(all_results)}") return all_results @@ -566,10 +532,7 @@ def print_stat_info( # Determine the true state of the current sample (for statistics and S curve) is_correct = False - if fail_type is not None: - # Already has a failure type, skip correctness check - pass - else: + if fail_type is None: datatype_data = performance_data.get("datatype", {}) eager_dtypes = datatype_data.get("eager", []) compiled_dtypes = datatype_data.get("compiled", [])