Files
paddlepaddle--paddle/python/paddle/jit/sot/symbolic/compile_cache.py
T
2026-07-13 12:40:42 +08:00

323 lines
10 KiB
Python

# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import inspect
from typing import TYPE_CHECKING
import paddle
from paddle.jit.profiler import EventGuard, event_register
from ..infer_meta import convert_meta_to_input_spec
from ..utils import (
ENV_SOT_EXPORT,
Cache,
InfoCollector,
NewSymbolHitRateInfo,
Singleton,
SIRToCodeMap,
StepInfoManager,
SubGraphInfo,
SubGraphRelationInfo,
log_do,
)
from .export import export
from .interpreter import compile_sir
if TYPE_CHECKING:
from paddle.static import InputSpec, Program
from .builder import StatementIRBuilder
from .statement_ir import ParametersHolder
def trace_back_frames():
frame = inspect.currentframe()
while frame.f_back is not None:
frame = frame.f_back
code = frame.f_code
paddle.framework.core.sot_set_with_graph(code)
def clear_eager_tensor_name(output_tensors):
for output_tensor in output_tensors:
output_tensor.name = ""
def _is_builtin_op(op):
dialect_name, opname = op.name().split(".")
return dialect_name == "builtin"
def _is_computation_op(op):
return not _is_builtin_op(op) and op.name() not in ["pd_op.data"]
class UniqueIdGenerator:
def __init__(self):
self._id = 0
def generate(self):
self._id += 1
return self._id
def __call__(self):
return self.generate()
class TensorIdAllocator(metaclass=Singleton):
TENSOR_ID_ATTR = "__tensor_id__"
def __init__(self):
self._id_generator = UniqueIdGenerator()
def allocate(self, tensor):
if not hasattr(tensor, self.TENSOR_ID_ATTR):
setattr(tensor, self.TENSOR_ID_ATTR, self._id_generator())
return getattr(tensor, self.TENSOR_ID_ATTR)
class FallbackWrapper:
"""
Used to store and call static graph methods generated by paddle.jit.to_static
"""
def __init__(self, compiled_fn, SIR, is_training: bool):
self.compiled_fn = compiled_fn
self.partial_program = None
self.concrete_program = None
self.SIR = SIR # for debug
self.is_training = is_training
self.exported = False
self.is_first_call = True
def graph_size(self):
if self.partial_program is None:
input_spec = convert_meta_to_input_spec(
tuple(
self.SIR.symbol_meta_map[symbol]
for symbol in self.SIR.inputs
)
)
(
self.concrete_program,
self.partial_program,
) = self.compiled_fn.get_concrete_program(input_spec)
self.partial_program.training = self.is_training
global_block_ops = self.concrete_program.main_program.global_block().ops
non_builtin_ops = list(filter(_is_computation_op, global_block_ops))
return len(non_builtin_ops)
def collect_new_symbol_hit_rate(self, inputs, outputs):
if not InfoCollector().need_collect(NewSymbolHitRateInfo):
return
input_tensor_ids = []
output_tensor_ids = []
assert len(inputs) == 1
assert isinstance(inputs[0], tuple)
for i, arg in enumerate(inputs[0]):
assert isinstance(arg, paddle.Tensor), f"Expect Tensor, got {arg}"
tensor_id = TensorIdAllocator().allocate(arg)
input_tensor_ids.append(tensor_id)
for i, out in enumerate(outputs):
assert isinstance(out, paddle.Tensor)
tensor_id = TensorIdAllocator().allocate(out)
output_tensor_ids.append(tensor_id)
InfoCollector().attach(
NewSymbolHitRateInfo, input_tensor_ids, output_tensor_ids
)
def collect_subgraph_relation(self, inputs, outputs, partial_program_layer):
if not InfoCollector().need_collect(SubGraphRelationInfo):
return
input_shape_infos = []
output_shape_infos = []
forward_input_values = partial_program_layer.program.program_attr['fx']
forward_output_values = partial_program_layer.program.program_attr['fo']
assert len(inputs) == 1
assert isinstance(inputs[0], tuple)
assert len(inputs[0]) == len(forward_input_values)
assert len(outputs) == len(forward_output_values)
for i, arg in enumerate(inputs[0]):
assert isinstance(arg, paddle.Tensor), f"Expect Tensor, got {arg}"
tensor_id = TensorIdAllocator().allocate(arg)
input_ir_shape = forward_input_values[i].shape
input_real_shape = arg.shape
input_shape_info = SubGraphRelationInfo.ConcreteShapeInfo(
tensor_id, input_ir_shape, input_real_shape
)
input_shape_infos.append(input_shape_info)
for i, out in enumerate(outputs):
assert isinstance(out, paddle.Tensor)
tensor_id = TensorIdAllocator().allocate(out)
output_ir_shape = forward_output_values[
partial_program_layer._outputs.quick_index_map[i]
].shape
output_real_shape = out.shape
output_shape_info = SubGraphRelationInfo.ConcreteShapeInfo(
tensor_id, output_ir_shape, output_real_shape
)
output_shape_infos.append(output_shape_info)
InfoCollector().attach(
SubGraphRelationInfo,
self.SIR.name,
input_shape_infos,
output_shape_infos,
self.is_first_call,
self.graph_size(),
)
def collect_subgraph_info(self, program: Program):
if not InfoCollector().need_collect(SubGraphInfo):
return
InfoCollector().attach(
SubGraphInfo,
str(program),
self.graph_size(),
self.SIR.name,
)
def update_compile_time_info(self, SIR, partial_program_layer):
if not self.is_first_call:
return
from ..opcode_translator.executor.executor_cache import (
OpcodeExecutorCache,
)
code = SIRToCodeMap().get(SIR)
assert code is not None, f"Cannot find code for SIR: {SIR}"
OpcodeExecutorCache().compile_time_stats.setdefault(code, 0)
OpcodeExecutorCache().compile_time_stats[code] += (
partial_program_layer._compile_time_counter.get_total_time()
)
@event_register(
lambda self, *args, **kwargs: f"FallbackWrapper: {self.SIR.name}"
)
def __call__(self, *args, **kwargs):
if StepInfoManager().need_back_trace:
trace_back_frames()
log_do(
2,
lambda: print("[FallbackWrapper] start run SIR: \n", self.SIR),
)
log_do(
4,
lambda: print(
self.compiled_fn.get_concrete_program(*args, **kwargs)[
1
].train_program
),
)
if self.partial_program is None:
with EventGuard("FallbackWrapper: get_concrete_program"):
(
self.concrete_program,
self.partial_program,
) = self.compiled_fn.get_concrete_program(*args, **kwargs)
self.partial_program.training = self.is_training
outputs = self.partial_program.sot_call(*args, **kwargs)
clear_eager_tensor_name(outputs)
log_do(
4,
lambda: print("[CompileCache] run sir forward success."),
)
self.collect_new_symbol_hit_rate(args, outputs)
self.collect_subgraph_relation(args, outputs, self.partial_program)
self.collect_subgraph_info(self.concrete_program.main_program)
self.update_compile_time_info(self.SIR, self.partial_program)
if ENV_SOT_EXPORT.get() != "" and not self.exported:
export(self.SIR, ENV_SOT_EXPORT.get())
self.exported = True
self.is_first_call = False
return outputs
class CompileSIRCache(Cache, metaclass=Singleton):
"""
Cache the compiled function of SIR
"""
def __init__(self):
super().__init__(weak=False)
def key_fn(
self,
builder: StatementIRBuilder,
sir_name: str,
parameters_holder: ParametersHolder,
input_spec: tuple[InputSpec | None, ...],
**kwargs,
):
"""
generate a hash key for a SIR
Args:
context: The context to compile
sir_name: The name of the sir to compile
build_strategy: The build strategy to compile
Returns:
The hash key of the SIR
"""
sir = builder.get_sir(sir_name)
# NOTE(dev): Is str(sir) a heavy operation ?
hash_key = hash(
(str(sir), *input_spec, id(parameters_holder), kwargs['training'])
)
return hash_key
def value_fn(
self,
builder: StatementIRBuilder,
sir_name: str,
parameters_holder: ParametersHolder,
input_spec: tuple[InputSpec | None, ...],
**kwargs,
):
"""
Generate static graph function
Args:
context: The context to compile
sir_name: The name of the sir to compile
build_strategy: The build strategy to compile
Returns:
The static graph function
"""
build_strategy = kwargs.get("build_strategy", None)
backend = kwargs.get("backend", None)
return FallbackWrapper(
paddle.jit.to_static(
compile_sir(builder, sir_name, parameters_holder),
input_spec=[input_spec],
build_strategy=build_strategy,
backend=backend,
full_graph=True,
),
builder.get_sir(sir_name),
is_training=kwargs['training'],
)