clef / code /models /common /llm_runtime /program_compiler.py
tt-hous's picture
Add files using upload-large-folder tool
2415c4c verified
Raw History Blame Contribute Delete
12.2 kB
# SPDX-FileCopyrightText: © 2026 Tenstorrent AI ULC
# SPDX-License-Identifier: Apache-2.0
"""Eager program readiness and compile-output resource ownership."""
from __future__ import annotations
import hashlib
import json
import math
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from typing import Any
import torch
import ttnn
from models.common.llm_runtime.tensor_resources import (
TensorResourceOrphan,
attach_cleanup_failures,
best_effort_deallocate_owned_tensors,
release_orphans,
)
_PROGRAM_KEY_DOMAIN = "tttv2.llm-runtime.program"
_PROGRAM_KEY_SCHEMA_VERSION = 1
@dataclass(frozen=True)
class ProgramKey:
"""Full content digest for one operation-produced program signature."""
digest: str
def __post_init__(self) -> None:
validate_sha256_digest(self.digest, "program")
@classmethod
def from_signature(cls, signature: Any) -> "ProgramKey":
return cls(signature_digest(_PROGRAM_KEY_DOMAIN, _PROGRAM_KEY_SCHEMA_VERSION, signature))
@dataclass(frozen=True)
class OutputSpec:
shape: tuple[int, ...]
dtype: Any
layout: Any = None
memory_config: Any = None
@classmethod
def from_value(cls, value: Any) -> "OutputSpec":
if isinstance(value, tuple):
if not value:
raise ValueError("Cannot derive an output specification from an empty tuple")
value = value[0]
if isinstance(value, torch.Tensor):
return cls(shape=tuple(value.shape), dtype=value.dtype)
if isinstance(value, ttnn.Tensor):
allocated = value.is_allocated() if hasattr(value, "is_allocated") else False
return cls(
shape=tuple(value.shape),
dtype=value.dtype,
layout=value.layout,
memory_config=value.spec.memory_config if allocated else None,
)
raise TypeError(f"Cannot derive an output specification from {type(value).__name__}")
@dataclass(frozen=True)
class CompiledProgram:
"""Program metadata retained independently from all trace state."""
key: ProgramKey
signature: Any
output_spec: OutputSpec
class ProgramCompiler:
"""Compile and retain the eager program set for one execution lane.
``EagerExecutor.compile_*`` derives an operation signature and calls
`compile`. Warmup registers the complete program set before
``TraceCompiler`` closes the compile gate. Forward execution then uses
`require_compiled` to reject unseen signatures after trace
activation. Trace artifacts remain in the separate trace registry.
"""
def __init__(self, mesh_device: Any, bound_cache_context: Callable[[], Any]):
self.mesh_device = mesh_device
self._bound_cache_context = bound_cache_context
self._programs: dict[ProgramKey, CompiledProgram] = {}
self._program_keys: dict[tuple[Any, ...], ProgramKey] = {}
self._compile_orphans: list[TensorResourceOrphan] = []
self._trace_capture_in_progress = False
self._trace_active = False
self._released = False
self._post_activation_compile_rejections = 0
# Public API
@property
def trace_capture_in_progress(self) -> bool:
return self._trace_capture_in_progress
@property
def trace_active(self) -> bool:
return self._trace_active
@property
def compile_orphan_count(self) -> int:
return len(self._compile_orphans)
@property
def compiled_programs(self) -> tuple[CompiledProgram, ...]:
"""Return an immutable snapshot of the authoritative program registry."""
return tuple(self._programs.values())
@property
def post_activation_compile_rejections(self) -> int:
"""Return unseen-program compile attempts rejected after activation."""
return self._post_activation_compile_rejections
def key_for(self, signature: Any) -> ProgramKey:
"""Return the stable program key for one operation signature."""
material = _canonical_value(_signature_key_material(signature))
key = self._program_keys.get(material)
if key is None:
key = ProgramKey.from_signature(signature)
self._program_keys[material] = key
return key
def compile(
self,
signature: Any,
invoke: Callable[[Any], Any],
*,
output_spec: Callable[[Any], OutputSpec] = OutputSpec.from_value,
release_output: Callable[[Any], Any] = lambda output: output,
expected_output_spec: OutputSpec | None = None,
) -> CompiledProgram:
"""Compile one signature and release its transient invocation output."""
self._ensure_live()
if self._compile_orphans:
raise RuntimeError("Cannot compile while unreleased compile outputs remain; clean up this compiler")
key = self.key_for(signature)
existing = self._programs.get(key)
if existing is not None:
_ensure_matching_signature(key, existing.signature, signature)
if expected_output_spec is not None and existing.output_spec != expected_output_spec:
raise ValueError(f"Program key {key.digest} was compiled with a different output contract")
return existing
if self._trace_capture_in_progress:
raise RuntimeError(f"Cannot compile uncompiled program key {key.digest} while trace capture is in progress")
if self._trace_active:
self._post_activation_compile_rejections += 1
raise RuntimeError(f"Cannot compile uncompiled program key {key.digest} after trace activation")
cache_context = self._bound_cache_context()
if cache_context is None:
raise RuntimeError("Paged KV cache must be allocated and bound before compilation")
output = invoke(cache_context)
owned_output = release_output(output)
try:
ttnn.synchronize_device(self.mesh_device)
spec = output_spec(output)
if expected_output_spec is not None and spec != expected_output_spec:
raise ValueError(f"Program key {key.digest} produced an unexpected output contract")
except BaseException as primary:
cleanup_failures = self._release_or_retain_compile_output(owned_output)
try:
ttnn.synchronize_device(self.mesh_device)
except BaseException as error:
cleanup_failures.append(error)
attach_cleanup_failures(primary, cleanup_failures)
raise
cleanup_failures = self._release_or_retain_compile_output(owned_output)
try:
ttnn.synchronize_device(self.mesh_device)
except BaseException as primary:
attach_cleanup_failures(primary, cleanup_failures)
raise
if cleanup_failures:
error = RuntimeError(f"Failed to deallocate {len(cleanup_failures)} compile output resource(s)")
attach_cleanup_failures(error, cleanup_failures)
raise error from cleanup_failures[0]
program = CompiledProgram(key=key, signature=signature, output_spec=spec)
self._programs[key] = program
return program
def require_compiled(self, key: ProgramKey, signature: Any | None = None) -> CompiledProgram:
"""Return registered program metadata or reject an unseen signature."""
self._ensure_live()
program = self._programs.get(key)
if program is None:
suffix = " after trace activation" if self._trace_active else ""
raise RuntimeError(f"Program key {key.digest} was not compiled{suffix}")
if signature is not None:
_ensure_matching_signature(key, program.signature, signature)
return program
def set_trace_capture_in_progress(self, value: bool) -> None:
self._ensure_live()
if not isinstance(value, bool):
raise TypeError("trace capture state must be bool")
if value and self._trace_active:
raise RuntimeError("Cannot begin trace capture after trace activation")
self._trace_capture_in_progress = value
def set_trace_active(self, value: bool) -> None:
self._ensure_live()
if not isinstance(value, bool):
raise TypeError("trace active state must be bool")
if value and self._trace_capture_in_progress:
raise RuntimeError("Trace capture must finish before trace activation")
self._trace_active = value
def cleanup(self) -> None:
"""Release retryable compile outputs and terminalize the registry."""
if self._released:
return
failures = release_orphans(self._compile_orphans)
if failures:
error = RuntimeError(f"Failed to release {len(failures)} compile output resource(s)")
attach_cleanup_failures(error, failures)
raise error from failures[0]
self._program_keys.clear()
self._trace_capture_in_progress = False
self._trace_active = False
self._released = True
# Private implementation
def _release_or_retain_compile_output(self, output: Any) -> list[BaseException]:
orphan = TensorResourceOrphan(output)
failures = best_effort_deallocate_owned_tensors(output, orphan.deallocated_tensor_ids)
if failures:
self._compile_orphans.append(orphan)
return failures
def _ensure_live(self) -> None:
if self._released:
raise RuntimeError("ProgramCompiler has been released")
# Public helpers
def signature_digest(domain: str, schema_version: int, signature: Any) -> str:
"""Return a stable SHA-256 digest for explicit signature key material."""
payload = (
("domain", domain),
("schema_version", schema_version),
("signature", _signature_key_material(signature)),
)
encoded = json.dumps(_canonical_value(payload), ensure_ascii=True, separators=(",", ":"))
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
def validate_sha256_digest(digest: str, domain: str) -> None:
"""Validate the full lowercase digest representation used by registry keys."""
if (
not isinstance(digest, str)
or len(digest) != 64
or any(character not in "0123456789abcdef" for character in digest)
):
raise ValueError(f"{domain} key digest must be a full lowercase SHA-256 hexadecimal digest")
# Private helpers
def _signature_key_material(signature: Any) -> tuple[Any, ...]:
"""Return the explicit tagged primitive tuple supplied by a signature."""
try:
material = signature.key_material
except AttributeError as error:
raise TypeError(f"{type(signature).__name__} must expose key_material") from error
if callable(material):
material = material()
if not isinstance(material, tuple):
raise TypeError("signature key_material must be a tuple")
return material
def _canonical_value(value: Any) -> Any:
if value is None:
return ("null",)
if isinstance(value, Enum):
if not isinstance(value.value, str):
raise TypeError("signature enum values must be stable strings")
return ("enum", value.value)
if isinstance(value, bool):
return ("bool", value)
if isinstance(value, int):
return ("int", str(value))
if isinstance(value, float):
if not math.isfinite(value):
raise ValueError("signature key material must not contain non-finite floats")
return ("float", value.hex())
if isinstance(value, str):
return ("str", value)
if isinstance(value, tuple):
return ("tuple", tuple(_canonical_value(item) for item in value))
raise TypeError(
"signature key material must contain only None, bool, int, finite float, str, stable enums, and tuples; "
f"got {type(value).__name__}"
)
def _ensure_matching_signature(key: ProgramKey, retained: Any, candidate: Any) -> None:
if retained != candidate:
raise RuntimeError(f"Program key collision for digest {key.digest}: retained signature differs")