@@ -347,96 +347,80 @@ def get_gcs_file_location_with_regex(
347347 return f"gs://{ bucket_name } /{ selected_blob .name } "
348348
349349
350- def get_gcs_profile_locations (dir_location : str ) -> str :
351- """
352- Find first gcs location matching regex `{dir_location}/.*/*xplane.pb`
353-
354- Returns:
355- The matched gcs location, or "" if no match
356- """
357- storage_client = storage .Client ()
358- url = urlparse (dir_location )
359- bucket_name = url .netloc
360- file_path = url .path .lstrip ("/" )
361- file_path_regex = re .compile (file_path + "/.*/*xplane.pb" )
362- matched_blobs = [
363- b
364- for b in storage_client .list_blobs (bucket_name , prefix = file_path )
365- if file_path_regex .match (b .name )
366- ]
367- if not matched_blobs :
368- logging .warning (
369- f"No objects matched supplied regex: { dir_location } /.*/*xplane.pb"
370- )
371- return ""
372-
373- selected_blob = matched_blobs [0 ]
374- return f"gs://{ bucket_name } /{ selected_blob .name } "
375-
376-
377- @task
378- def download_profile (
379- process_id : str , dir_location : str | airflow .XComArg
380- ) -> str :
381- """
382- Find the first file_location matching regex `{dir_location}/.*/*xplane.pb`
383- Create tmp_dir = f"/tmp/profile-{process_id}"
384- Download profile to tmp_location = f"{tmp_dir}/gke.xplane.pb"
385-
386- Returns:
387- tmp_dir, or "" if no match
388- """
389- if isinstance (dir_location , airflow .XComArg ):
390- dir_location = dir_location .resolve (get_current_context ())
391- file_location = get_gcs_profile_locations (dir_location )
392- # no match profile
393- if not file_location :
394- return ""
395- print (f"For local download, run: gcloud storage cp { file_location } ." )
396- # unique directory to accomodate intermediate files from profile extraction
397- tmp_dir = f"/tmp/profile-{ process_id } "
398- os .makedirs (tmp_dir , exist_ok = False )
399- download_object_from_gcs (file_location , f"{ tmp_dir } /gke.xplane.pb" )
400- return tmp_dir
401-
402-
403350@task .virtualenv (
404351 task_id = "process_profile_metrics" ,
405352 requirements = ["tensorboard_plugin_profile==2.19.4" ],
406- system_site_packages = False ,
353+ system_site_packages = True ,
407354)
408- def xplane_to_metrics (input_dir : str ) -> dict :
355+ def xplane_to_metrics (dir_location : str | airflow . XComArg ) -> dict :
409356 """
410- Extract metrics from xplane file `{input_dir}/gke.xplane.pb`.
411- Remove temporory input_dir generated by `download_profile`.
357+ Find the first profile matching regex `{dir_location}/.*/*xplane.pb`.
358+ Download profile to temporary directory.
359+ Extract metrics from xplane file.
412360
413361 Returns:
414- A dictionary of profile metrics, or None if input_dir is ""
362+ A dictionary of profile metrics, or None if no match
415363 """
416364 # pylint: disable=redefined-outer-name, import-outside-toplevel, reimported
417- from tensorboard_plugin_profile .convert import raw_to_tool_data
418365 import json
419366 import logging
420- import shutil
367+ import re
368+ import tempfile
369+ from urllib .parse import urlparse
370+ import airflow
371+ from google .cloud import storage
372+ from tensorboard_plugin_profile .convert import raw_to_tool_data
421373 # pylint: enable=redefined-outer-name, import-outside-toplevel, reimported
422374
423- if not input_dir :
424- return None
425- input_path = f"{ input_dir } /gke.xplane.pb"
426- print (f"processing xplane file: { input_path } " )
375+ # --- Find and Download Profile ---
376+ # pylint: disable-next=redefined-outer-name
377+ def download_object_from_gcs (
378+ source_location : str , destination_location : str
379+ ) -> None :
380+ """Download object from GCS bucket.
381+
382+ Args:
383+ source_location: The full path of a file in GCS.
384+ destination_location: The local path of the file.
385+ """
386+ storage_client = storage .Client ()
387+ bucket_name = source_location .split ("/" )[2 ]
388+ object_name = "/" .join (source_location .split ("/" )[3 :])
389+
390+ bucket = storage_client .bucket (bucket_name )
391+ blob = bucket .blob (object_name )
392+ blob .download_to_filename (destination_location )
393+ logging .info (
394+ ("Download file from" f" { source_location } to { destination_location } " )
395+ )
427396
428- def cleanup (input_dir ):
429- # remove temporary directory generated by `download_profile`
430- if input_dir .startswith ("/tmp/profile-" ):
431- shutil .rmtree (input_dir )
432- print (f"remove temporary directory: { input_dir } " )
397+ def get_gcs_profile_location (dir_location : str ) -> str :
398+ """
399+ Find first gcs location matching regex `{dir_location}/.*/*xplane.pb`.
400+
401+ Returns:
402+ The matched gcs location, or "" if no match
403+ """
404+ storage_client = storage .Client ()
405+ url = urlparse (dir_location )
406+ bucket_name = url .netloc
407+ file_path = url .path .lstrip ("/" )
408+ file_path_regex = re .compile (file_path + "/.*/*xplane.pb" )
409+ for b in storage_client .list_blobs (bucket_name , prefix = file_path ):
410+ if file_path_regex .match (b .name ):
411+ return f"gs://{ bucket_name } /{ b .name } "
412+ logging .warning (
413+ f"No objects matched supplied regex: { dir_location } /.*/*xplane.pb"
414+ )
415+ return ""
433416
417+ # --- Extract Metrics from Profile ---
434418 def round_number (number : float | str , decimal : int ) -> float :
435419 if isinstance (number , str ):
436420 number = float (number )
437421 return float (f"{ number :.{decimal }f} " )
438422
439- def xplane_tool (input_path : str , tool : str , params : dict ) -> dict :
423+ def get_tool_data (input_path : str , tool : str , params : dict ) -> dict :
440424 data , content_type = raw_to_tool_data .xspace_to_tool_data (
441425 xspace_paths = [input_path ],
442426 tool = tool ,
@@ -447,96 +431,111 @@ def xplane_tool(input_path: str, tool: str, params: dict) -> dict:
447431 parsed_data = json .loads (data )
448432 return parsed_data
449433
450- out = {}
451- # check available tools
452- available_tools = set (
453- raw_to_tool_data .xspace_to_tool_names (xspace_paths = [input_path ])
454- )
455- # 1 overview_page
456- # generate ALL_HOSTS.op_stats.pb
457- if "overview_page" not in available_tools :
458- logging .warning ("overview_page: unavailable tool" )
459- else :
434+ def get_tool_metrics (input_path : str ) -> dict :
435+ out = {}
436+ # check available tools
437+ available_tools = set (
438+ raw_to_tool_data .xspace_to_tool_names (xspace_paths = [input_path ])
439+ )
440+ # 1 overview_page
441+ # generate ALL_HOSTS.op_stats.pb
442+ if "overview_page" not in available_tools :
443+ logging .warning ("overview_page: unavailable tool" )
444+ else :
445+ try :
446+ overview_page = get_tool_data (
447+ input_path , tool = "overview_page" , params = {}
448+ )
449+ out .update ({
450+ "Device Type" : overview_page [2 ]["p" ]["device_type" ],
451+ "Device Core Count" : int (
452+ overview_page [2 ]["p" ]["device_core_count" ]
453+ ),
454+ "Average Tensor Core Step Time (ms)" : float (
455+ overview_page [1 ]["p" ]["steptime_ms_average" ]
456+ ),
457+ })
458+ except ValueError as e :
459+ logging .warning (e )
460+
461+ # 2 op_profile
462+ if "op_profile" not in available_tools :
463+ logging .warning ("op_profile: unavailable tool" )
464+ return out
460465 try :
461- overview_page = xplane_tool (input_path , tool = "overview_page " , params = {})
466+ op_profile = get_tool_data (input_path , tool = "op_profile " , params = {})
462467 out .update ({
463- "device_type" : overview_page [2 ]["p" ]["device_type" ],
464- "device_core_count" : int (overview_page [2 ]["p" ]["device_core_count" ]),
465- "Average Tensor Core Step Time (ms)" : float (
466- overview_page [1 ]["p" ]["steptime_ms_average" ]
468+ "TPU FLOPS Utilization (%): exclude_idle" : round_number (
469+ op_profile ["byProgramExcludeIdle" ]["metrics" ]["flops" ] * 100 , 2
470+ ),
471+ "HBM Bandwidth Utilization (%): exclude_idle" : round_number (
472+ op_profile ["byProgramExcludeIdle" ]["metrics" ]["bandwidthUtils" ][0 ]
473+ * 100 ,
474+ 2 ,
467475 ),
468476 })
469477 except ValueError as e :
470478 logging .warning (e )
471-
472- # 2 op_profile
473- if "op_profile" not in available_tools :
474- logging .warning ("op_profile: unavailable tool" )
475- cleanup (input_dir )
476- return out
477- try :
478- op_profile = xplane_tool (input_path , tool = "op_profile" , params = {})
479- out .update ({
480- "TPU FLOPS Utilization (%): exclude_idle" : round_number (
481- op_profile ["byProgramExcludeIdle" ]["metrics" ]["flops" ] * 100 , 2
482- ),
483- "HBM Bandwidth Utilization (%): exclude_idle" : round_number (
484- op_profile ["byProgramExcludeIdle" ]["metrics" ]["bandwidthUtils" ][0 ]
485- * 100 ,
486- 2 ,
487- ),
488- })
489- except ValueError as e :
490- logging .warning (e )
491- cleanup (input_dir )
492- return out
493-
494- # 3 memory_viewer: jit_train_step
495- # generate jit*.hlo_proto.pb
496- if "memory_viewer" not in available_tools :
497- logging .warning ("memory_viewer: unavailable tool" )
498- cleanup (input_dir )
499- return out
500- # find jit_train_step from 2
501- # example: "jit_train_step(4869159985936022652)"
502- jit_train_step = None
503- for program in op_profile ["byProgramExcludeIdle" ]["children" ]:
504- if program ["name" ].startswith ("jit_train_step" ):
505- jit_train_step = program ["name" ]
506- break
507- if jit_train_step is None :
508- logging .warning ("memory_viewer: cannot find jit_train_step in op_profile" )
509- cleanup (input_dir )
479+ return out
480+
481+ # 3 memory_viewer: jit_train_step
482+ # generate jit*.hlo_proto.pb
483+ if "memory_viewer" not in available_tools :
484+ logging .warning ("memory_viewer: unavailable tool" )
485+ return out
486+ # find jit_train_step from 2
487+ # example: "jit_train_step(4869159985936022652)"
488+ jit_train_step = None
489+ for program in op_profile ["byProgramExcludeIdle" ]["children" ]:
490+ if program ["name" ].startswith ("jit_train_step" ):
491+ jit_train_step = program ["name" ]
492+ break
493+ if jit_train_step is None :
494+ logging .warning ("memory_viewer: cannot find jit_train_step in op_profile" )
495+ return out
496+ # 3.1 hbm
497+ MEMORY_SPACE_HBM = "0"
498+ try :
499+ memory_viewer_hbm = get_tool_data (
500+ input_path ,
501+ "memory_viewer" ,
502+ params = {"host" : jit_train_step , "memory_space" : MEMORY_SPACE_HBM },
503+ )
504+ out ["Peak memory allocation (MiB): jit_train_step, HBM" ] = round_number (
505+ memory_viewer_hbm ["totalBufferAllocationMib" ], 2
506+ )
507+ except ValueError as e :
508+ logging .warning (e )
509+ # 3.2 host
510+ MEMORY_SPACE_HOST = "5"
511+ try :
512+ memory_viewer_host = get_tool_data (
513+ input_path ,
514+ "memory_viewer" ,
515+ params = {"host" : jit_train_step , "memory_space" : MEMORY_SPACE_HOST },
516+ )
517+ out ["Peak memory allocation (MiB): jit_train_step, host" ] = round_number (
518+ memory_viewer_host ["totalBufferAllocationMib" ], 2
519+ )
520+ except ValueError as e :
521+ logging .warning (e )
510522 return out
511- # 3.1 hbm
512- MEMORY_SPACE_HBM = "0"
513- try :
514- memory_viewer_hbm = xplane_tool (
515- input_path ,
516- "memory_viewer" ,
517- params = {"host" : jit_train_step , "memory_space" : MEMORY_SPACE_HBM },
518- )
519- out ["Peak memory allocation (MiB): jit_train_step, HBM" ] = round_number (
520- memory_viewer_hbm ["totalBufferAllocationMib" ], 2
521- )
522- except ValueError as e :
523- logging .warning (e )
524- # 3.2 host
525- MEMORY_SPACE_HOST = "5"
526- try :
527- memory_viewer_host = xplane_tool (
528- input_path ,
529- "memory_viewer" ,
530- params = {"host" : jit_train_step , "memory_space" : MEMORY_SPACE_HOST },
531- )
532- out ["Peak memory allocation (MiB): jit_train_step, host" ] = round_number (
533- memory_viewer_host ["totalBufferAllocationMib" ], 2
534- )
535- except ValueError as e :
536- logging .warning (e )
537523
538- cleanup (input_dir )
539- return out
524+ # --- Main Logic ---
525+ # Find the first file_location matching regex `{dir_location}/.*/*xplane.pb`
526+ if isinstance (dir_location , airflow .XComArg ):
527+ dir_location = dir_location .resolve (get_current_context ())
528+ file_location = get_gcs_profile_location (dir_location )
529+ if file_location :
530+ print (f"For local download, run: gcloud storage cp { file_location } ." )
531+ # Temporary directory for file download and extraction cache
532+ with tempfile .TemporaryDirectory () as tmp_dir :
533+ input_path = f"{ tmp_dir } /gke.xplane.pb"
534+ download_object_from_gcs (file_location , input_path )
535+ # Extract profile
536+ return get_tool_metrics (input_path )
537+ # No match profile
538+ return None
540539
541540
542541def process_profile (
0 commit comments