Download code/models/tt_transformers/scripts/op_perf_results.py from raahemnabeel/qwen3-coder-next: direct link, hf CLI and curl.
- Browser
- Download file 7.77 kB
-
https://huggingface.co/raahemnabeel/qwen3-coder-next/resolve/main/code/models/tt_transformers/scripts/op_perf_results.py
- Command line
-
hf download hf://raahemnabeel/qwen3-coder-next/code/models/tt_transformers/scripts/op_perf_results.py
-
curl -L -o op_perf_results.py https://huggingface.co/raahemnabeel/qwen3-coder-next/resolve/main/code/models/tt_transformers/scripts/op_perf_results.py
7.77 kB
| # SPDX-FileCopyrightText: © 2023 Tenstorrent USA, Inc. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import csv | |
| from argparse import ArgumentParser | |
| from collections import defaultdict | |
| def main(): | |
| parser = ArgumentParser( | |
| "Parse an op perf results CSV and show performance data using the min allgather time and max other time over devices, optionally only for a specific signpost region." | |
| ) | |
| parser.add_argument("csv", help="Input CSV file") | |
| parser.add_argument("--all", help="Show all times for each device", action="store_true") | |
| parser.add_argument("--signpost", help="Only include data after this signpost and before any others") | |
| parser.add_argument("--skip-last", help="Do not include timings from the last N ops", type=int, default=0) | |
| parser.add_argument("--skip-first", help="Do not include timings from the first N ops", type=int, default=0) | |
| parser.add_argument("--prefill", help="Prefill mode: will compute tok/s", action="store_true") | |
| parser.add_argument("--seqlen", help="Sequence length used for prefill statistics.", type=int, default=0) | |
| parser.add_argument( | |
| "--estimate-full-model", | |
| help="Estimate the full model performance by multiplying by N and adding back in the skipped ops", | |
| type=int, | |
| default=0, | |
| ) | |
| parser.add_argument("--write-ops-to-csv", help="Write the summarized ops to a CSV file", type=str, default=None) | |
| args = parser.parse_args() | |
| header, rows = read_rows(args.csv) | |
| blocks, signposts_seen = make_blocks(header, rows, args.signpost) | |
| if args.signpost and not args.signpost in signposts_seen: | |
| print(f'Error: signpost "{args.signpost}" was not found in this file') | |
| print(f"Valid signposts are: {signposts_seen}") | |
| return | |
| print(f'{"Op":20} {"Time (us)"}') | |
| if args.skip_first: | |
| print(f"The following ops from the start of the run are not included in summary statistics:") | |
| for block in blocks[: args.skip_first] if args.skip_first else blocks: | |
| print(block.long_str() if args.all else block.short_str()) | |
| print(f"Ops included in the summary statistics:") | |
| skipped_ops = blocks[: args.skip_first] | |
| blocks = blocks[args.skip_first :] | |
| else: | |
| skipped_ops = [] | |
| for block in blocks[: -args.skip_last] if args.skip_last else blocks: | |
| print(block.long_str() if args.all else block.short_str()) | |
| if args.skip_last: | |
| print(f"The following ops from the end of the run are not included in summary statistics below:") | |
| for block in blocks[-args.skip_last :]: | |
| print(block.long_str() if args.all else block.short_str()) | |
| skipped_ops += blocks[-args.skip_last :] | |
| blocks = blocks[: -args.skip_last] | |
| total_time_ns = sum(block.time() for block in blocks) | |
| total_time_s = total_time_ns / 1e9 | |
| tokens_per_s = 1 / total_time_s | |
| if args.prefill: | |
| sequences_per_s = tokens_per_s | |
| tokens_per_s *= args.seqlen | |
| print(f"Tokens/s: {tokens_per_s:.2f} ({total_time_s*1000*1000:.1f} us latency, {sequences_per_s:.2f} seq/s)") | |
| else: | |
| print(f"Tokens/s/user: {tokens_per_s:.2f} ({total_time_s*1000*1000:.1f} us latency)") | |
| if args.estimate_full_model: | |
| total_time_ns *= args.estimate_full_model | |
| total_time_ns += sum(block.time() for block in skipped_ops) | |
| total_time_s = total_time_ns / 1e9 | |
| tokens_per_s = 1 / total_time_s | |
| if args.prefill: | |
| sequences_per_s = tokens_per_s | |
| tokens_per_s *= args.seqlen | |
| print( | |
| f"Estimated full model ({args.estimate_full_model} * above + skipped ops) tokens/s: {tokens_per_s:.2f} ({total_time_s*1000*1000:.1f} us latency, {sequences_per_s:.2f} seq/s)" | |
| ) | |
| else: | |
| print( | |
| f"Estimated full model ({args.estimate_full_model} * above + skipped ops) tokens/s/user: {tokens_per_s:.2f} ({total_time_s*1000*1000:.1f} us latency)" | |
| ) | |
| if signposts_seen and not args.signpost: | |
| print(f"Warning - this file contains the following signposts that were not used for this analysis:") | |
| for s in signposts_seen: | |
| print(f' "{s}"') | |
| print("Rerun with --signpost to show only the performance for a specific signpost region") | |
| if args.write_ops_to_csv: | |
| write_blocks_to_csv(blocks, args.write_ops_to_csv) | |
| return tokens_per_s | |
| def read_rows(csv_file): | |
| with open(csv_file, "r") as f: | |
| reader = csv.reader(f) | |
| header = next(reader) | |
| rows = list(reader) | |
| return header, rows | |
| class Block: | |
| def __init__(self, op_name, times): | |
| self.op_name = op_name | |
| self.times = times | |
| def time(self): | |
| return min(self.times) if "AllGather" in self.op_name or "ReduceScatter" in self.op_name else max(self.times) | |
| def short_str(self): | |
| short_name = self.op_name.split("::")[-1].split(")")[0] | |
| time_range = max(self.times) - min(self.times) | |
| return f"{short_name:20} {self.time()/1000:-6.0f} ± {time_range/1000:-5.0f}" | |
| def long_str(self): | |
| short_name = self.op_name.split("::")[-1].split(")")[0] | |
| return f"{short_name:20} {self.time()/1000:-6.0f} <-" + " | ".join(f"{t/1000:-5.0f}" for t in self.times) | |
| def __repr__(self): | |
| return f"Block({self.op_name}, {self.times})" | |
| def make_blocks(header, rows, signpost): | |
| """Perf dumps have one row per device in order, repeated for each op | |
| This returns a list of blocks, where each block has an op name | |
| and a list of times for each device. | |
| """ | |
| # group rows by device then merge them together | |
| block_by_device = defaultdict(list) | |
| stop_on_signpost = False | |
| signposts_seen = [] | |
| OP_CODE = header.index("OP CODE") | |
| OP_TYPE = header.index("OP TYPE") | |
| DEVICE_ID = header.index("DEVICE ID") | |
| FW_DURATION = header.index("DEVICE FW DURATION [ns]") | |
| block_op_name = None | |
| for row in rows: | |
| op_name = row[OP_CODE] | |
| op_type = row[OP_TYPE] | |
| if op_type == "signpost": | |
| signposts_seen.append(op_name) | |
| if stop_on_signpost: | |
| break | |
| elif op_name == signpost: | |
| # clear any previous data and stop on the next signpost | |
| stop_on_signpost = True | |
| block_by_device = defaultdict(list) | |
| elif op_type == "tt_dnn_device": | |
| device_id = int(row[DEVICE_ID]) | |
| time = int(row[FW_DURATION]) | |
| block_by_device[device_id].append(Block(op_name, [time])) | |
| # merge each device block into a single block with all the device times, | |
| # checking that the op name matches | |
| # blocks_by_device is a dict of device_id -> Block | |
| # we want to get a list of Block (with all device times) | |
| device_ids = list(sorted(block_by_device.keys())) | |
| merged_blocks = block_by_device[device_ids[0]] | |
| for device_id in device_ids[1:]: | |
| assert len(block_by_device[device_id]) == len( | |
| merged_blocks | |
| ), f"Device {device_id} has {len(block_by_device[device_id])} ops, expected {len(merged_blocks)} from previous devices" | |
| for row, b in enumerate(block_by_device[device_id]): | |
| assert ( | |
| b.op_name == merged_blocks[row].op_name | |
| ), f"Op name mismatch at row {row}: device {device_id} has {b.op_name} != {merged_blocks[row].op_name}" | |
| merged_blocks[row].times += b.times | |
| return merged_blocks, signposts_seen | |
| def write_blocks_to_csv(blocks, csv_file): | |
| with open(csv_file, "w") as f: | |
| writer = csv.writer(f) | |
| writer.writerow(["Op", "Time (us)"]) | |
| for block in blocks: | |
| writer.writerow([block.op_name, block.time()]) | |
| if __name__ == "__main__": | |
| main() | |