1010import ctypes
1111import functools
1212import gc
13+ import hashlib
1314import logging
1415import os
1516import shutil
@@ -401,12 +402,17 @@ def _on_off_compile_spec_value(spec: CompileSpec) -> bool:
401402 return value == "ON"
402403
403404
404- def _write_aoti_weights_blob (weights , blob_path : str ) -> None :
405- """Stream AOTI tensor storages without creating a model-sized bytes object ."""
405+ def _write_aoti_weights_blob (weights , blob_path : str ) -> bytes :
406+ """Stream AOTI tensor storages to disk and return their SHA-256 digest ."""
406407 _trim_host_memory ()
407408 tensors = [tensor for tensor , _ in weights .values ()]
408409 all_cuda = all (tensor .is_cuda for tensor in tensors )
409410 chunk_size = 8 * 1024 * 1024
411+ digest = hashlib .sha256 ()
412+
413+ def write_chunk (output , chunk ) -> None :
414+ digest .update (chunk )
415+ output .write (chunk )
410416
411417 with open (blob_path , "wb" ) as output :
412418 for tensor in tensors :
@@ -420,20 +426,21 @@ def _write_aoti_weights_blob(weights, blob_path: str) -> None:
420426 ).set_ (storage , 0 , (nbytes ,), (1 ,))
421427 for offset in range (0 , nbytes , chunk_size ):
422428 cpu_chunk = byte_tensor [offset : offset + chunk_size ].cpu ()
423- output . write ( memoryview (cpu_chunk .numpy ()))
429+ write_chunk ( output , memoryview (cpu_chunk .numpy ()))
424430 del byte_tensor , cpu_chunk
425431 elif nbytes :
426432 raw_array = (ctypes .c_ubyte * nbytes ).from_address (storage .data_ptr ())
427433 raw_view = memoryview (raw_array ).cast ("B" )
428434 for offset in range (0 , nbytes , chunk_size ):
429- output . write ( raw_view [offset : offset + chunk_size ])
435+ write_chunk ( output , raw_view [offset : offset + chunk_size ])
430436 del raw_view , raw_array
431437 # Match AOTInductor's binary_blob layout: CUDA-only constants are
432438 # packed, while CPU/mixed constants are aligned to 64 bytes.
433439 if not all_cuda and (padding := (- nbytes ) % 64 ):
434- output . write ( bytes (padding ))
440+ write_chunk ( output , bytes (padding ))
435441 del storage
436442 _trim_host_memory ()
443+ return digest .digest ()
437444
438445
439446@final
@@ -447,6 +454,11 @@ class CudaBackend(AotiBackend, BackendDetails):
447454 using the Executorch runtime.
448455 """
449456
457+ # AOTI calls materialize_weights_blob immediately before load_weights_blob
458+ # for a given output path. A new materialization overwrites any digest left
459+ # behind by an export that aborted before the consumer ran.
460+ _materialized_blob_hashes : Dict [str , bytes ] = {}
461+
450462 @classmethod
451463 def get_device_name (cls ) -> str :
452464 return "cuda"
@@ -562,8 +574,10 @@ def load_weights_blob(
562574 """
563575 if not cls ._is_low_memory_mode (compile_specs ):
564576 return super ().load_weights_blob (blob_path , compile_specs )
565- blob_data = FileBackedData .move_from (blob_path )
566- return blob_data , blob_data .sha256 ().hex ()
577+ known_hash = cls ._materialized_blob_hashes .pop (blob_path , None )
578+ blob_data = FileBackedData .move_from (blob_path , sha256 = known_hash )
579+ weights_blob_hash = known_hash or blob_data .sha256 ()
580+ return blob_data , weights_blob_hash .hex ()
567581
568582 @classmethod
569583 def materialize_weights_blob (
@@ -593,7 +607,9 @@ def materialize_weights_blob(
593607 if so_path is None :
594608 raise RuntimeError (f"Expected a CUDA AOTI .wrapper.so output, got { paths } " )
595609 blob_path = os .path .splitext (so_path )[0 ] + "_weights.blob"
596- _write_aoti_weights_blob (weights [0 ], blob_path )
610+ cls ._materialized_blob_hashes [blob_path ] = _write_aoti_weights_blob (
611+ weights [0 ], blob_path
612+ )
597613
598614 # Forcing the external-weights ABI makes Inductor emit an empty blob
599615 # path alongside the Weights object. Replace that file in place and do
0 commit comments