ttnn-visualizer 0.63.1__py3-none-any.whl → 0.65.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ttnn_visualizer/app.py CHANGED
@@ -25,7 +25,7 @@ from ttnn_visualizer.exceptions import (
25
25
  )
26
26
  from ttnn_visualizer.instances import create_instance_from_local_paths
27
27
  from ttnn_visualizer.settings import Config, DefaultConfig
28
- from ttnn_visualizer.utils import find_gunicorn_path
28
+ from ttnn_visualizer.utils import find_gunicorn_path, get_app_data_directory
29
29
  from werkzeug.debug import DebuggedApplication
30
30
  from werkzeug.middleware.proxy_fix import ProxyFix
31
31
 
@@ -55,8 +55,9 @@ def create_app(settings_override=None):
55
55
  static_folder=config.STATIC_ASSETS_DIR,
56
56
  static_url_path=f"{config.BASE_PATH}static",
57
57
  )
58
- logging.basicConfig(level=app.config.get("LOG_LEVEL", "INFO"))
59
58
 
59
+ # logging.basicConfig(level=app.config.get("LOG_LEVEL", "DEBUG"))
60
+ logging.basicConfig(level=logging.DEBUG)
60
61
  app.config.from_object(config)
61
62
 
62
63
  if settings_override:
@@ -118,6 +119,8 @@ def extensions(app: flask.Flask):
118
119
  flask_static_digest.init_app(app)
119
120
  if app.config["USE_WEBSOCKETS"]:
120
121
  socketio.init_app(app)
122
+
123
+ Path(app.config["APP_DATA_DIRECTORY"]).mkdir(parents=True, exist_ok=True)
121
124
  db.init_app(app)
122
125
 
123
126
  if app.config["USE_WEBSOCKETS"]:
@@ -192,6 +195,23 @@ def parse_args():
192
195
  parser.add_argument(
193
196
  "--tt-metal-home", help="Specify a TT-Metal home path", default=None
194
197
  )
198
+ parser.add_argument(
199
+ "--host",
200
+ type=str,
201
+ help="Host to bind to (default: auto-detected based on environment)",
202
+ default=None,
203
+ )
204
+ parser.add_argument(
205
+ "--port",
206
+ type=str,
207
+ help="Port to bind to (default: 8000)",
208
+ default=None,
209
+ )
210
+ parser.add_argument(
211
+ "--server",
212
+ action="store_true",
213
+ help="Bind to all network interfaces (0.0.0.0) and enable server mode. Useful for servers and VMs",
214
+ )
195
215
  parser.add_argument(
196
216
  "-d",
197
217
  "--daemon",
@@ -250,13 +270,56 @@ def main():
250
270
 
251
271
  args = parse_args()
252
272
 
273
+ # Handle host/port CLI overrides
274
+ # Priority: CLI args > env vars > auto-detection (in settings.py)
275
+ # Note: We need to set env vars before creating Config, but also
276
+ # manually update the config object in case it was already instantiated
277
+ if args.host:
278
+ os.environ["HOST"] = args.host
279
+ print(f"🌐 Binding to host: {args.host} (from --host flag)")
280
+ elif args.server:
281
+ os.environ["HOST"] = "0.0.0.0"
282
+ os.environ["SERVER_MODE"] = "true"
283
+ print("🌐 Binding to all interfaces (0.0.0.0) via --server flag")
284
+ print("🖥️ Server mode enabled")
285
+
286
+ if args.port:
287
+ os.environ["PORT"] = args.port
288
+ print(f"🔌 Binding to port: {args.port}")
289
+
253
290
  config = cast(DefaultConfig, Config())
291
+
292
+ # Apply CLI overrides directly to config object
293
+ # (Config is a singleton that may have been created before we set env vars)
294
+ if args.host:
295
+ config.HOST = args.host
296
+ elif args.server:
297
+ config.HOST = "0.0.0.0"
298
+ config.SERVER_MODE = True
299
+
300
+ if args.port:
301
+ config.PORT = args.port
302
+
303
+ # Recalculate GUNICORN_BIND with the updated values
304
+ config.GUNICORN_BIND = f"{config.HOST}:{config.PORT}"
305
+
254
306
  instance_id = None
255
307
 
256
308
  # Display mode information first (using config only, no DB needed)
257
309
  if args.tt_metal_home:
310
+ os.environ["TT_METAL_HOME"] = args.tt_metal_home
258
311
  config.TT_METAL_HOME = args.tt_metal_home
259
312
 
313
+ if not os.getenv("APP_DATA_DIRECTORY"):
314
+ config.APP_DATA_DIRECTORY = get_app_data_directory(
315
+ args.tt_metal_home, config.APPLICATION_DIR
316
+ )
317
+ # Recalculate database path with new APP_DATA_DIRECTORY
318
+ _db_file_path = str(
319
+ Path(config.APP_DATA_DIRECTORY) / f"ttnn_{config.DB_VERSION}.db"
320
+ )
321
+ config.SQLALCHEMY_DATABASE_URI = f"sqlite:///{_db_file_path}"
322
+
260
323
  display_mode_info_without_db(config)
261
324
 
262
325
  # If profiler/performance paths are provided, create an instance
@@ -398,6 +398,7 @@ class OpsPerformanceReportQueries:
398
398
  "total_percent",
399
399
  "bound",
400
400
  "op_code",
401
+ "device",
401
402
  "device_time",
402
403
  "op_to_op_gap",
403
404
  "cores",
@@ -430,12 +431,26 @@ class OpsPerformanceReportQueries:
430
431
  "flops_std",
431
432
  ]
432
433
 
434
+ STACKED_REPORT_COLUMNS_WITH_DEVICE = [
435
+ "percent",
436
+ "op_code",
437
+ "device",
438
+ "device_time_sum_us",
439
+ "ops_count",
440
+ "flops_min",
441
+ "flops_max",
442
+ "flops_mean",
443
+ "flops_std",
444
+ ]
445
+
433
446
  PASSTHROUGH_COLUMNS = {
434
447
  "pm_ideal_ns": "PM IDEAL [ns]",
435
448
  }
436
449
 
437
- DEFAULT_SIGNPOST = None
450
+ DEFAULT_START_SIGNPOST = None
451
+ DEFAULT_END_SIGNPOST = None
438
452
  DEFAULT_IGNORE_SIGNPOSTS = True
453
+ DEFAULT_PRINT_SIGNPOSTS = True
439
454
  DEFAULT_MIN_PERCENTAGE = 0.5
440
455
  DEFAULT_ID_RANGE = None
441
456
  DEFAULT_NO_ADVICE = False
@@ -444,6 +459,7 @@ class OpsPerformanceReportQueries:
444
459
  DEFAULT_NO_HOST_OPS = False
445
460
  DEFAULT_NO_STACKED_REPORT = False
446
461
  DEFAULT_NO_STACK_BY_IN0 = True
462
+ DEFAULT_MERGE_DEVICES = True
447
463
 
448
464
  @classmethod
449
465
  def generate_report(cls, instance, **kwargs):
@@ -451,31 +467,39 @@ class OpsPerformanceReportQueries:
451
467
  csv_file = StringIO(raw_csv)
452
468
  csv_output_file = tempfile.mktemp(suffix=".csv")
453
469
  csv_stacked_output_file = tempfile.mktemp(suffix=".csv")
454
- signpost = kwargs.get("signpost", cls.DEFAULT_SIGNPOST)
470
+ start_signpost = kwargs.get("start_signpost", cls.DEFAULT_START_SIGNPOST)
471
+ end_signpost = kwargs.get("end_signpost", cls.DEFAULT_END_SIGNPOST)
455
472
  ignore_signposts = cls.DEFAULT_IGNORE_SIGNPOSTS
473
+ print_signposts = kwargs.get("print_signposts", cls.DEFAULT_PRINT_SIGNPOSTS)
456
474
  stack_by_in0 = kwargs.get("stack_by_in0", cls.DEFAULT_NO_STACK_BY_IN0)
475
+ no_host_ops = kwargs.get("hide_host_ops", cls.DEFAULT_NO_HOST_OPS)
476
+ merge_devices = kwargs.get("merge_devices", cls.DEFAULT_MERGE_DEVICES)
457
477
 
458
- if signpost:
478
+ if start_signpost or end_signpost:
459
479
  ignore_signposts = False
460
480
 
461
481
  # perf_report currently generates a PNG alongside the CSV using the same temp name - we'll just delete it afterwards
462
- stacked_png_file = os.path.splitext(csv_output_file)[0] + ".png"
482
+ stacked_png_file = csv_stacked_output_file + ".png"
483
+ stacked_csv_file = csv_stacked_output_file + ".csv"
463
484
 
464
485
  try:
465
486
  perf_report.generate_perf_report(
466
487
  [csv_file],
467
- signpost,
488
+ start_signpost,
489
+ end_signpost,
468
490
  ignore_signposts,
491
+ print_signposts,
469
492
  cls.DEFAULT_MIN_PERCENTAGE,
470
493
  cls.DEFAULT_ID_RANGE,
471
494
  csv_output_file,
472
495
  cls.DEFAULT_NO_ADVICE,
473
496
  cls.DEFAULT_TRACING_MODE,
474
497
  cls.DEFAULT_RAW_OP_CODES,
475
- cls.DEFAULT_NO_HOST_OPS,
498
+ no_host_ops,
476
499
  cls.DEFAULT_NO_STACKED_REPORT,
477
500
  stack_by_in0,
478
501
  csv_stacked_output_file,
502
+ not merge_devices,
479
503
  )
480
504
  except Exception as e:
481
505
  logger.error(f"Error generating performance report: {e}")
@@ -552,16 +576,23 @@ class OpsPerformanceReportQueries:
552
576
 
553
577
  stacked_report = []
554
578
 
555
- if os.path.exists(csv_stacked_output_file):
579
+ if os.path.exists(stacked_csv_file):
556
580
  try:
557
- with open(csv_stacked_output_file, newline="") as csvfile:
581
+ with open(stacked_csv_file, newline="") as csvfile:
558
582
  reader = csv.reader(csvfile, delimiter=",")
559
583
  next(reader, None)
560
584
 
585
+ # Use the appropriate column list based on merge_devices flag
586
+ stacked_columns = (
587
+ cls.STACKED_REPORT_COLUMNS_WITH_DEVICE
588
+ if not merge_devices
589
+ else cls.STACKED_REPORT_COLUMNS
590
+ )
591
+
561
592
  for row in reader:
562
593
  processed_row = {
563
594
  column: row[index]
564
- for index, column in enumerate(cls.STACKED_REPORT_COLUMNS)
595
+ for index, column in enumerate(stacked_columns)
565
596
  if index < len(row)
566
597
  }
567
598
 
@@ -577,12 +608,10 @@ class OpsPerformanceReportQueries:
577
608
  except csv.Error as e:
578
609
  raise DataFormatError() from e
579
610
  finally:
580
- os.unlink(csv_stacked_output_file)
611
+ os.unlink(stacked_csv_file)
581
612
  if os.path.exists(stacked_png_file):
582
613
  os.unlink(stacked_png_file)
583
614
 
584
- stacked_report.append(processed_row)
585
-
586
615
  return {
587
616
  "report": report,
588
617
  "stacked_report": stacked_report,
@@ -7,7 +7,11 @@ from pathlib import Path
7
7
 
8
8
  from dotenv import load_dotenv
9
9
  from sqlalchemy.pool import NullPool
10
- from ttnn_visualizer.utils import str_to_bool
10
+ from ttnn_visualizer.utils import (
11
+ get_app_data_directory,
12
+ is_running_in_container,
13
+ str_to_bool,
14
+ )
11
15
 
12
16
  load_dotenv()
13
17
 
@@ -41,9 +45,13 @@ class DefaultConfig(object):
41
45
  PERFORMANCE_DIRECTORY_NAME = "performance-reports"
42
46
  NPE_DIRECTORY_NAME = "npe-reports"
43
47
  APPLICATION_DIR = os.path.abspath(os.path.join(__file__, "..", os.pardir))
44
- APP_DATA_DIRECTORY = os.getenv("APP_DATA_DIRECTORY", APPLICATION_DIR)
45
- STATIC_ASSETS_DIR = Path(APPLICATION_DIR).joinpath("ttnn_visualizer", "static")
46
48
  TT_METAL_HOME = os.getenv("TT_METAL_HOME", None)
49
+ APP_DATA_DIRECTORY = os.getenv(
50
+ "APP_DATA_DIRECTORY",
51
+ get_app_data_directory(TT_METAL_HOME, APPLICATION_DIR),
52
+ )
53
+
54
+ STATIC_ASSETS_DIR = Path(APPLICATION_DIR).joinpath("ttnn_visualizer", "static")
47
55
  SEND_FILE_MAX_AGE_DEFAULT = 0
48
56
 
49
57
  LAUNCH_BROWSER_ON_START = str_to_bool(os.getenv("LAUNCH_BROWSER_ON_START", "true"))
@@ -75,7 +83,7 @@ class DefaultConfig(object):
75
83
  GUNICORN_WORKERS = os.getenv("GUNICORN_WORKERS", "1")
76
84
  GUNICORN_TIMEOUT = os.getenv("GUNICORN_TIMEOUT", "60")
77
85
  PORT = os.getenv("PORT", "8000")
78
- HOST = os.getenv("HOST", "localhost")
86
+ HOST = os.getenv("HOST", "0.0.0.0" if is_running_in_container() else "localhost")
79
87
  DEV_SERVER_PORT = "5173"
80
88
  DEV_SERVER_HOST = "localhost"
81
89
 
@@ -1 +1 @@
1
- import{I as s}from"./index-voJy5fZe.js";import{I as r}from"./index-BZITDwoa.js";import{p as n,I as c}from"./index-B_nutJ_M.js";function p(t,a){const o=n(t);return a===c.STANDARD?s[o]:r[o]}export{s as IconSvgPaths16,r as IconSvgPaths20,p as getIconPaths};
1
+ import{I as s}from"./index-voJy5fZe.js";import{I as r}from"./index-BZITDwoa.js";import{p as n,I as c}from"./index-DMzz7de9.js";function p(t,a){const o=n(t);return a===c.STANDARD?s[o]:r[o]}export{s as IconSvgPaths16,r as IconSvgPaths20,p as getIconPaths};
@@ -0,0 +1,2 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/allPaths-B6-2k5TG.js","assets/index-voJy5fZe.js","assets/index-BZITDwoa.js","assets/index-DMzz7de9.js","assets/index-C7m_PE7l.css"])))=>i.map(i=>d[i]);
2
+ import{_ as e}from"./index-DMzz7de9.js";const s=async(t,a)=>{const{getIconPaths:o}=await e(async()=>{const{getIconPaths:r}=await import("./allPaths-B6-2k5TG.js");return{getIconPaths:r}},__vite__mapDeps([0,1,2,3,4]));return o(t,a)};export{s as allPathsLoader};