Skip to content

Commit 4798a67

Browse files
authored
[CUDA] Preserve fallback when remerging PTEs (#22670)
This PR fixes the issue when we try to merge multi-sm PTEs with regular ones. Also add relevant tests.
1 parent 13ec99a commit 4798a67

3 files changed

Lines changed: 376 additions & 47 deletions

File tree

‎backends/cuda/merge_ptes.py‎

Lines changed: 110 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
@dataclass(frozen=True)
5252
class CudaPteInput:
53-
"""One native CUDA export and its optional external tensor data."""
53+
"""One CUDA PTE input and its optional external tensor data."""
5454

5555
pte_path: Path
5656
ptd_path: Optional[Path]
@@ -377,6 +377,54 @@ def _compact_delegate_data(program: Program) -> None:
377377
program.backend_delegate_data = payloads
378378

379379

380+
def _select_merge_fallback(
381+
inherited_fallbacks: Sequence[Tuple[_Artifact, CudaAotiVariant]],
382+
fallback_artifact: Optional[_Artifact],
383+
fallback_delegates: Optional[Dict[Tuple[str, int], CudaAotiMetadata]],
384+
reference: _Artifact,
385+
reference_metadata: CudaAotiMetadata,
386+
identity: Tuple[str, int],
387+
) -> Optional[Tuple[_Artifact, CudaAotiVariant]]:
388+
if fallback_artifact is not None:
389+
assert fallback_delegates is not None
390+
metadata = fallback_delegates[identity]
391+
_validate_shared_weights(
392+
reference, reference_metadata, fallback_artifact, metadata, identity
393+
)
394+
if len(metadata.variants) != 1 or not metadata.variants[0].ptx_compute:
395+
raise ValueError(
396+
f"Fallback PTE {fallback_artifact.source.pte_path} must contain "
397+
f"exactly one PTX-capable variant for delegate {identity}"
398+
)
399+
return fallback_artifact, replace(metadata.variants[0], fallback_only=True)
400+
401+
if not inherited_fallbacks:
402+
return None
403+
fallback_source, fallback = inherited_fallbacks[0]
404+
try:
405+
fallback_data = fallback_source.pte_named_data[fallback.so_blob_key]
406+
except KeyError as error:
407+
raise ValueError(
408+
f"{fallback_source.source.pte_path} does not contain CUDA SO "
409+
f"{fallback.so_blob_key!r}"
410+
) from error
411+
for candidate_source, candidate in inherited_fallbacks[1:]:
412+
try:
413+
candidate_data = candidate_source.pte_named_data[candidate.so_blob_key]
414+
except KeyError as error:
415+
raise ValueError(
416+
f"{candidate_source.source.pte_path} does not contain CUDA SO "
417+
f"{candidate.so_blob_key!r}"
418+
) from error
419+
if candidate != fallback or candidate_data != fallback_data:
420+
raise ValueError(
421+
f"CUDA fallback variants differ for delegate {identity} between "
422+
f"{fallback_source.source.pte_path} and "
423+
f"{candidate_source.source.pte_path}"
424+
)
425+
return fallback_source, fallback
426+
427+
380428
def _merge_delegate_variants(
381429
regular_artifacts: Sequence[_Artifact],
382430
regular_delegates: Sequence[Dict[Tuple[str, int], CudaAotiMetadata]],
@@ -389,13 +437,17 @@ def _merge_delegate_variants(
389437
) -> List[CudaAotiVariant]:
390438
variants = []
391439
target_sms = set()
440+
inherited_fallbacks: List[Tuple[_Artifact, CudaAotiVariant]] = []
392441
reference = regular_artifacts[0]
393442
for artifact, delegates in zip(regular_artifacts, regular_delegates):
394443
metadata = delegates[identity]
395444
_validate_shared_weights(
396445
reference, reference_metadata, artifact, metadata, identity
397446
)
398447
for variant in metadata.variants:
448+
if variant.fallback_only:
449+
inherited_fallbacks.append((artifact, variant))
450+
continue
399451
if variant.target_sm in target_sms:
400452
raise ValueError(f"Duplicate CUDA target sm{variant.target_sm}")
401453
target_sms.add(variant.target_sm)
@@ -407,7 +459,7 @@ def _merge_delegate_variants(
407459
f"{variant.so_blob_key!r}"
408460
) from error
409461
merged_store.add_named_data(variant.so_blob_key, so_data)
410-
variants.append(replace(variant, ptx_compute=0, fallback_only=False))
462+
variants.append(replace(variant, ptx_compute=0))
411463
provenance.append(
412464
CudaPteProvenance(
413465
delegate=identity,
@@ -419,39 +471,36 @@ def _merge_delegate_variants(
419471
)
420472

421473
variants.sort(key=lambda variant: variant.target_sm)
422-
if fallback_artifact is not None:
423-
assert fallback_delegates is not None
424-
metadata = fallback_delegates[identity]
425-
_validate_shared_weights(
426-
reference, reference_metadata, fallback_artifact, metadata, identity
427-
)
428-
fallback_variants = [
429-
variant for variant in metadata.variants if variant.ptx_compute
430-
]
431-
if len(fallback_variants) != 1:
432-
raise ValueError(
433-
f"Fallback PTE {fallback_artifact.source.pte_path} must contain "
434-
f"exactly one PTX-capable variant for delegate {identity}"
435-
)
436-
fallback = replace(fallback_variants[0], fallback_only=True)
437-
try:
438-
so_data = fallback_artifact.pte_named_data[fallback.so_blob_key]
439-
except KeyError as error:
440-
raise ValueError(
441-
f"{fallback_artifact.source.pte_path} does not contain CUDA SO "
442-
f"{fallback.so_blob_key!r}"
443-
) from error
444-
merged_store.add_named_data(fallback.so_blob_key, so_data)
445-
variants.append(fallback)
446-
provenance.append(
447-
CudaPteProvenance(
448-
delegate=identity,
449-
kind="ptx-fallback",
450-
target_sm=fallback.target_sm,
451-
ptx_compute=fallback.ptx_compute,
452-
source_pte=fallback_artifact.source.pte_path,
453-
)
474+
fallback_selection = _select_merge_fallback(
475+
inherited_fallbacks,
476+
fallback_artifact,
477+
fallback_delegates,
478+
reference,
479+
reference_metadata,
480+
identity,
481+
)
482+
if fallback_selection is None:
483+
return variants
484+
fallback_source, fallback = fallback_selection
485+
486+
try:
487+
so_data = fallback_source.pte_named_data[fallback.so_blob_key]
488+
except KeyError as error:
489+
raise ValueError(
490+
f"{fallback_source.source.pte_path} does not contain CUDA SO "
491+
f"{fallback.so_blob_key!r}"
492+
) from error
493+
merged_store.add_named_data(fallback.so_blob_key, so_data)
494+
variants.append(fallback)
495+
provenance.append(
496+
CudaPteProvenance(
497+
delegate=identity,
498+
kind="ptx-fallback",
499+
target_sm=fallback.target_sm,
500+
ptx_compute=fallback.ptx_compute,
501+
source_pte=fallback_source.source.pte_path,
454502
)
503+
)
455504
return variants
456505

457506

@@ -490,14 +539,32 @@ def _prepare_merged_output(reference: _Artifact) -> Tuple[Program, NamedDataStor
490539

491540
merged_store = NamedDataStore()
492541
if reference.pte.named_data is not None:
493-
merged_store.merge_named_data_store(reference.pte.named_data)
542+
cuda_so_keys = _cuda_so_keys(reference)
543+
merged_store.merge_named_data_store(
544+
NamedDataStoreOutput(
545+
buffers=reference.pte.named_data.buffers,
546+
pte_data={
547+
key: entry
548+
for key, entry in reference.pte.named_data.pte_data.items()
549+
if key not in cuda_so_keys
550+
},
551+
external_data={
552+
filename: {
553+
key: entry
554+
for key, entry in entries.items()
555+
if key not in cuda_so_keys
556+
}
557+
for filename, entries in reference.pte.named_data.external_data.items()
558+
},
559+
)
560+
)
494561
return merged_program, merged_store
495562

496563

497564
def merge_cuda_pte_files_with_provenance(
498565
inputs: Sequence[CudaPteInput], fallback: Optional[CudaPteInput] = None
499566
) -> CudaPteMergeResult:
500-
"""Merge exact-SM CUDA exports and an optional PTX-only fallback source."""
567+
"""Merge CUDA variants and an optional PTX-capable fallback source."""
501568
if torch.version.hip is not None:
502569
raise RuntimeError(
503570
"CUDA PTE merging supports only NVIDIA CUDA and is not supported on ROCm"
@@ -584,16 +651,16 @@ def merge_cuda_pte_files(
584651

585652
def _parse_args() -> argparse.Namespace:
586653
parser = argparse.ArgumentParser(
587-
description=("Merge exact-SM CUDA PTEs with an optional explicit PTX fallback")
654+
description=("Merge CUDA PTE variants with an optional explicit PTX fallback")
588655
)
589656
parser.add_argument(
590657
"--input-pte",
591658
action="append",
592659
required=True,
593660
type=Path,
594661
help=(
595-
"Regular CUDA PTE contributing exact-SM native cubins; the first "
596-
"input supplies common data"
662+
"CUDA PTE contributing native variants and, for a previous merge output, "
663+
"an inherited fallback; the first input supplies common data"
597664
),
598665
)
599666
parser.add_argument(
@@ -606,7 +673,10 @@ def _parse_args() -> argparse.Namespace:
606673
parser.add_argument(
607674
"--fallback-pte",
608675
type=Path,
609-
help="Single CUDA PTE contributing only the PTX runtime fallback",
676+
help=(
677+
"CUDA PTE containing exactly one PTX-capable variant to use as the "
678+
"runtime fallback"
679+
),
610680
)
611681
parser.add_argument(
612682
"--fallback-ptd",

0 commit comments

Comments
 (0)