1818
1919import argparse
2020import os
21+ import time
2122from datetime import timedelta
2223
2324import torch
4243 "tensor_parallel" : {"status" : "pending" , "error" : None },
4344 "data_parallel" : {"status" : "pending" , "error" : None },
4445 "pipeline_parallel" : {"status" : "pending" , "error" : None },
46+ "gpu_hardware" : {"status" : "pending" , "error" : None },
47+ "gpu_computation" : {"status" : "pending" , "error" : None },
4548}
4649
4750
@@ -493,6 +496,272 @@ def check_communication():
493496 print ("=" * 60 )
494497
495498
499+ def check_hardware_single ():
500+ """Single GPU hardware check without distributed calls"""
501+ try :
502+ import pynvml
503+
504+ pynvml .nvmlInit ()
505+
506+ print ("Checking GPU hardware" )
507+
508+ device_count = pynvml .nvmlDeviceGetCount ()
509+ all_passed = True
510+ errors = []
511+ for i in range (device_count ):
512+ handle = pynvml .nvmlDeviceGetHandleByIndex (i )
513+ gpu_name = pynvml .nvmlDeviceGetName (handle )
514+
515+ print (f"=== Checking GPU { i } : { gpu_name } ===" )
516+
517+ temp = pynvml .nvmlDeviceGetTemperature (handle , pynvml .NVML_TEMPERATURE_GPU )
518+ print (f"Current GPU temperature: { temp } °C" )
519+ if temp >= 90 :
520+ all_passed = False
521+ errors .append (f"GPU { i } overheat: { temp } °C" )
522+ elif temp > 85 :
523+ print (f"Warning: GPU { i } high temperature: { temp } °C" )
524+
525+ power_usage = pynvml .nvmlDeviceGetPowerUsage (handle ) / 1000.0
526+ power_limit = pynvml .nvmlDeviceGetEnforcedPowerLimit (handle ) / 1000.0
527+ power_ratio = power_usage / power_limit if power_limit > 0 else 0.0
528+ print (f"Power usage: { power_usage :.2f} W / { power_limit :.2f} W" )
529+ if power_ratio > 0.9 :
530+ print (f"GPU { i } power usage high: { power_usage :.1f} W ({ power_ratio :.0%} )" )
531+
532+ mem_info = pynvml .nvmlDeviceGetMemoryInfo (handle )
533+ memory_utilization = (float (mem_info .used ) / float (mem_info .total )) * 100
534+
535+ print (f"Total memory: { float (mem_info .total ) / (1024 ** 2 ):.2f} MB" )
536+ print (f"Used memory: { float (mem_info .used ) / (1024 ** 2 ):.2f} MB" )
537+ print (f"GPU { i } memory utilization rate: { memory_utilization :.2f} %" )
538+
539+ if memory_utilization >= 98.0 :
540+ all_passed = False
541+ errors .append (f"GPU { i } memory almost full: { memory_utilization * 100 :.1f} %" )
542+
543+ pynvml .nvmlShutdown ()
544+ if all_passed :
545+ log_check_result ("gpu_hardware" , status = "passed" )
546+ return True
547+ else :
548+ log_check_result ("gpu_hardware" , status = "failed" , error = "; " .join (errors ))
549+ return False
550+ except ImportError :
551+ log_check_result ("gpu_hardware" , status = "failed" , error = "pynvml is not installed" )
552+ return False
553+ except Exception as e :
554+ log_check_result ("gpu_hardware" , status = "failed" , error = str (e ))
555+ return False
556+
557+
558+ def check_hardware ():
559+ """Distributed wrapper: run single check once per node and summarize result"""
560+
561+ args = _GLOBAL_ARGS
562+ rank = dist .get_rank ()
563+
564+ if rank == 0 :
565+ print ("\n " + "=" * 60 )
566+ print ("PHASE 2: GPU HARDWARE TESTING" )
567+ print ("=" * 60 )
568+
569+ if args .local_rank == 0 :
570+ check_hardware_single ()
571+ if rank == 0 :
572+ print ("\n GPU hardware checking phase completed" )
573+ print ("=" * 60 )
574+
575+
576+ def check_computation_for_different_dtype (dtype , name ):
577+ args = _GLOBAL_ARGS
578+ check_tensor = torch .randn (4096 , 4096 , dtype = dtype ).to (f"cuda:{ args .local_rank } " )
579+ result = torch .matmul (check_tensor , check_tensor )
580+ if torch .any (torch .isnan (result )):
581+ print (f"{ name } failed: nan is detected in result" )
582+ return False
583+ elif torch .any (torch .isinf (result )):
584+ print (f"{ name } failed: inf is detected in result" )
585+ return False
586+ else :
587+ return True
588+
589+
590+ def check_computation_endurance ():
591+ args = _GLOBAL_ARGS
592+ start_time = time .time ()
593+ iteration = 0
594+
595+ while time .time () - start_time < 60 :
596+ iteration += 1
597+
598+ a = torch .randn (4096 , 4096 ).to (f"cuda:{ args .local_rank } " )
599+ b = torch .randn (4096 , 4096 ).to (f"cuda:{ args .local_rank } " )
600+ result1 = torch .matmul (a , b )
601+
602+ c = torch .randn (4096 , 4096 ).to (f"cuda:{ args .local_rank } " )
603+ result2 = torch .inverse (c )
604+
605+ if torch .any (torch .isnan (result1 )) or torch .any (torch .isnan (result2 )):
606+ print (f"check_computation_endurance failed: nan detected in iteration { iteration } " )
607+ return False
608+
609+ return True
610+
611+
612+ def check_ecc_error ():
613+ """Check ECC Error through matrix multiplication operations"""
614+ try :
615+ args = _GLOBAL_ARGS
616+ rank = dist .get_rank ()
617+ device = f"cuda:{ args .local_rank } "
618+
619+ # Perform multiple matrix operations to stress check memory
620+ for i in range (5 ):
621+ # Create large tensors to stress GPU memory
622+ tensor_a = torch .randn (2048 , 2048 , dtype = torch .float32 , device = device )
623+ tensor_b = torch .randn (2048 , 2048 , dtype = torch .float32 , device = device )
624+
625+ # Perform matrix multiplication that could trigger ECC errors
626+ result = torch .matmul (tensor_a , tensor_b )
627+
628+ # Check for abnormal values that might indicate ECC errors
629+ if torch .any (torch .isnan (result )):
630+ print (f"ECC Error Detection: NaN detected in iteration { i } " )
631+ return False
632+ if torch .any (torch .isinf (result )):
633+ print (f"ECC Error Detection: Inf detected in iteration { i } " )
634+ return False
635+
636+ torch .cuda .empty_cache ()
637+ if rank == 0 :
638+ print ("ECC Error Detection: No errors detected" )
639+ return True
640+
641+ except torch .cuda .OutOfMemoryError as e :
642+ print (f"ECC Error Detection failed: GPU out of memory - { e } " )
643+ return False
644+ except RuntimeError as e :
645+ if "cuda" in str (e ).lower () or "gpu" in str (e ).lower ():
646+ print (f"ECC Error Detection failed: GPU runtime error - { e } " )
647+ return False
648+ else :
649+ print (f"ECC Error Detection failed: Runtime error - { e } " )
650+ return False
651+ except Exception as e :
652+ print (f"ECC Error Detection failed: Unexpected error - { e } " )
653+ return False
654+
655+
656+ def check_computation_single ():
657+ """Single GPU computation check"""
658+ print ("Checking GPU computation capabilities..." )
659+ checks = [
660+ ("Float" , torch .float32 ),
661+ ("Double" , torch .double ),
662+ ("Half" , torch .half ),
663+ ]
664+ all_pass = True
665+ for display_name , dtype in checks :
666+ ok = check_computation_for_different_dtype (
667+ dtype = dtype , name = f"check_calculation_{ display_name .lower ()} "
668+ )
669+ print (f"{ display_name } computation: { 'PASS' if ok else 'FAIL' } " )
670+ if not ok :
671+ all_pass = False
672+ print ("Starting 60-second endurance check..." )
673+ endurance_result = check_computation_endurance ()
674+ if not endurance_result :
675+ all_pass = False
676+ print (f"Endurance check: { 'PASS' if endurance_result else 'FAIL' } " )
677+ ecc_error_result = check_ecc_error ()
678+ if not ecc_error_result :
679+ all_pass = False
680+ print (f"ECC error check: { 'PASS' if ecc_error_result else 'FAIL' } " )
681+ if all_pass :
682+ log_check_result ("gpu_computation" , "passed" )
683+ return True
684+ else :
685+ log_check_result ("gpu_computation" , "failed" )
686+ return False
687+
688+
689+ def check_computation ():
690+ """Test GPU computation capabilities with distributed coordination"""
691+ args = _GLOBAL_ARGS
692+ rank = dist .get_rank ()
693+
694+ if rank == 0 :
695+ print ("\n " + "=" * 60 )
696+ print ("PHASE 3: GPU COMPUTATION CHECKING" )
697+ print ("=" * 60 )
698+
699+ # Check individual computation capabilities
700+ check_functions = [
701+ ("Float computation" , torch .float32 , "check_computation_float" ),
702+ ("Double computation" , torch .double , "check_computation_double" ),
703+ ("Half computation" , torch .half , "check_computation_half" ),
704+ ("Endurance check (60s)" , check_computation_endurance ),
705+ ("ECC Error Detection" , check_ecc_error ),
706+ ]
707+
708+ all_passed = True
709+ failed_checks = []
710+
711+ for check_name , * rest in check_functions :
712+ if rank == 0 :
713+ print (f"\n Testing { check_name } ..." )
714+
715+ try :
716+ if isinstance (rest [0 ], torch .dtype ):
717+ _ , dtype , report_name = check_name , rest [0 ], rest [1 ]
718+ result = check_computation_for_different_dtype (dtype , report_name )
719+ else :
720+ check_func = rest [0 ]
721+ result = check_func ()
722+ if not result :
723+ all_passed = False
724+ failed_checks .append (check_name )
725+
726+ except Exception as e :
727+ result = False
728+ all_passed = False
729+ failed_checks .append (check_name )
730+ if rank == 0 :
731+ print (f"✗ { check_name } failed with exception: { e } " )
732+
733+ try :
734+ result_tensor = torch .zeros (args .world_size ).to (f"cuda:{ args .local_rank } " )
735+ result_tensor [args .rank ] = 1.0 if result else 0.0
736+ dist .all_reduce (result_tensor , dist .ReduceOp .SUM )
737+
738+ if args .rank == 0 :
739+ expected_tensor = torch .ones_like (result_tensor ).to (f"cuda:{ args .local_rank } " )
740+ if torch .allclose (result_tensor , expected_tensor , atol = 1e-6 ):
741+ print (f"{ check_name } passed" )
742+ else :
743+ print (f"{ check_name } failed" )
744+ except Exception as e :
745+ if rank == 0 :
746+ print (f"⚠ Warning: Failed to gather { check_name } results: { e } " )
747+
748+ try :
749+ dist .barrier ()
750+ except Exception as e :
751+ if rank == 0 :
752+ print (f"⚠ Warning: Calculation check barrier failed: { e } " )
753+
754+ if all_passed :
755+ log_check_result ("gpu_computation" , "passed" )
756+ else :
757+ error_msg = f"Failed checks: { ', ' .join (failed_checks )} "
758+ log_check_result ("gpu_computation" , "failed" , error_msg )
759+
760+ if rank == 0 :
761+ print ("\n GPU computation checking phase completed" )
762+ print ("=" * 60 )
763+
764+
496765def parse_args ():
497766 parser = argparse .ArgumentParser (description = "GPU Health Check arguments" )
498767 parser .add_argument (
@@ -535,18 +804,28 @@ def print_check_summary():
535804 rank = dist .get_rank () if dist .is_initialized () else 0
536805 if rank != 0 :
537806 return
538-
807+ args = _GLOBAL_ARGS
808+ world_size = args .world_size
539809 print ("=" * 60 )
540810 print ("GPU HEALTH CHECK SUMMARY" )
541811 print ("=" * 60 )
812+ if world_size == 1 :
813+ results = list (_CHECK_RESULTS .items ())[- 2 :]
814+ scope_desc = "HARDWARE ONLY (single GPU)"
815+ else :
816+ results = list (_CHECK_RESULTS .items ())
817+ scope_desc = "ALL CHECKS (multi GPU)"
818+
819+ print (f"Scope: { scope_desc } " )
820+ print ("-" * 60 )
542821
543- total_checks = len (_CHECK_RESULTS )
544- passed_checks = sum (1 for result in _CHECK_RESULTS . values () if result ["status" ] == "passed" )
545- failed_checks = sum (1 for result in _CHECK_RESULTS . values () if result ["status" ] == "failed" )
546- skipped_checks = sum (1 for result in _CHECK_RESULTS . values () if result ["status" ] == "skipped" )
547- pending_checks = sum (1 for result in _CHECK_RESULTS . values () if result ["status" ] == "pending" )
822+ total_checks = len (results )
823+ passed_checks = sum (1 for _ , r in results if r ["status" ] == "passed" )
824+ failed_checks = sum (1 for _ , r in results if r ["status" ] == "failed" )
825+ skipped_checks = sum (1 for _ , r in results if r ["status" ] == "skipped" )
826+ pending_checks = sum (1 for _ , r in results if r ["status" ] == "pending" )
548827
549- for check_name , result in _CHECK_RESULTS . items () :
828+ for check_name , result in results :
550829 status_icon = (
551830 "✓" if result ["status" ] == "passed" else "✗" if result ["status" ] == "failed" else "⚠"
552831 )
@@ -595,7 +874,24 @@ def main():
595874 if rank == 0 :
596875 print ("Single process mode detected" )
597876 print ("Running basic GPU hardware and computation checks..." )
598- # TODO: add GPU hardware and computation checks
877+
878+ # PHASE 1: Check gpu hardware
879+ safe_check_execution (check_hardware_single , "gpu_hardware" , timeout_seconds = 60 )
880+ # PHASE 2: Check gpu computation capabilities
881+ safe_check_execution (
882+ check_computation_single , "gpu_computation" , timeout_seconds = 300
883+ ) # 5 minutes for endurance check
884+ # Print final summary
885+ if rank == 0 :
886+ print_check_summary ()
887+ SINGLE_CHECK_RESULTS = ["gpu_hardware" , "gpu_computation" ]
888+ failed_count = sum (
889+ 1 for r in SINGLE_CHECK_RESULTS if _CHECK_RESULTS [r ]["status" ] == "failed"
890+ )
891+ if failed_count > 0 :
892+ import sys
893+
894+ sys .exit (1 )
599895 return
600896
601897 if rank == 0 :
@@ -612,7 +908,11 @@ def main():
612908 # PHASE 1: Check parallel communication
613909 check_communication ()
614910
615- # TODO: add GPU hardware and computation checks
911+ # PHASE 2: Check gpu hardware
912+ check_hardware ()
913+
914+ # PHASE 3: Check gpu computation capabilities
915+ check_computation ()
616916
617917 if rank == 0 :
618918 print ("=" * 60 )
@@ -636,11 +936,11 @@ def main():
636936 if rank == 0 :
637937 print_check_summary ()
638938
639- failed_count = sum (1 for r in _CHECK_RESULTS .values () if r ["status" ] == "failed" )
640- if failed_count > 0 :
641- import sys
939+ failed_count = sum (1 for r in _CHECK_RESULTS .values () if r ["status" ] == "failed" )
940+ if failed_count > 0 :
941+ import sys
642942
643- sys .exit (1 )
943+ sys .exit (1 )
644944
645945
646946if __name__ == "__main__" :
0 commit comments