189 lines
5.5 KiB
Python
189 lines
5.5 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
|
|
import random
|
|
import unittest
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import patch
|
|
|
|
from test_case_base import (
|
|
TestCaseBase,
|
|
test_instruction_translator_cache_context,
|
|
)
|
|
|
|
import paddle
|
|
from paddle.jit.sot.opcode_translator.custom_code import CustomCode
|
|
from paddle.jit.sot.opcode_translator.executor.executor_cache import (
|
|
OpcodeExecutorCache,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from types import FrameType
|
|
|
|
|
|
def fake_frames() -> tuple[
|
|
FrameType,
|
|
FrameType,
|
|
FrameType,
|
|
FrameType,
|
|
FrameType,
|
|
]:
|
|
def fake_inner_fn_1():
|
|
frame = inspect.currentframe()
|
|
assert frame is not None
|
|
return frame
|
|
|
|
def fake_inner_fn_2():
|
|
frame = inspect.currentframe()
|
|
assert frame is not None
|
|
return frame
|
|
|
|
def fake_inner_fn_3():
|
|
frame = inspect.currentframe()
|
|
assert frame is not None
|
|
return frame
|
|
|
|
def fake_inner_fn_4():
|
|
frame = inspect.currentframe()
|
|
assert frame is not None
|
|
return frame
|
|
|
|
def fake_inner_fn_5():
|
|
frame = inspect.currentframe()
|
|
assert frame is not None
|
|
return frame
|
|
|
|
return (
|
|
fake_inner_fn_1(),
|
|
fake_inner_fn_2(),
|
|
fake_inner_fn_3(),
|
|
fake_inner_fn_4(),
|
|
fake_inner_fn_5(),
|
|
)
|
|
|
|
|
|
(
|
|
FRAME_1,
|
|
FRAME_2,
|
|
FRAME_3,
|
|
FRAME_4,
|
|
FRAME_5,
|
|
) = fake_frames()
|
|
|
|
|
|
class GuardCode:
|
|
def __init__(self, recompile):
|
|
self.func = lambda frame: recompile
|
|
self.mirror_guard = self.func
|
|
self.expr = f"lambda frame: {recompile}"
|
|
self.inlined_expr = f"lambda frame: {recompile}"
|
|
self.__globals__ = {}
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return self.func(*args, **kwargs)
|
|
|
|
|
|
def mock_start_translate(frame: FrameType, **kwargs):
|
|
translate_map = {
|
|
FRAME_1: (
|
|
CustomCode(FRAME_2.f_code, False),
|
|
GuardCode(True),
|
|
[paddle.framework.core.DummyGuardNode()],
|
|
),
|
|
FRAME_3: (
|
|
CustomCode(FRAME_4.f_code, False),
|
|
GuardCode(False),
|
|
[paddle.framework.core.DummyGuardNode(False)],
|
|
), # Always re-compile
|
|
FRAME_5: (
|
|
CustomCode(None, False),
|
|
lambda frame: True,
|
|
[paddle.framework.core.DummyGuardNode()],
|
|
),
|
|
}
|
|
return translate_map[frame]
|
|
|
|
|
|
class TestOpcodeExecutorCache(unittest.TestCase):
|
|
def reset(self):
|
|
global translate_count
|
|
translate_count = 0
|
|
OpcodeExecutorCache().clear()
|
|
|
|
@patch(
|
|
"paddle.jit.sot.opcode_translator.executor.executor_cache.start_translate",
|
|
mock_start_translate,
|
|
)
|
|
def test_cache_hit(self):
|
|
with test_instruction_translator_cache_context() as ctx:
|
|
translated_code_1 = OpcodeExecutorCache()(FRAME_1)
|
|
assert translated_code_1 is not None
|
|
self.assertEqual(translated_code_1.code, FRAME_2.f_code)
|
|
self.assertEqual(ctx.translate_count, 1)
|
|
# cache hit
|
|
translated_code_2 = OpcodeExecutorCache()(FRAME_1)
|
|
assert translated_code_2 is not None
|
|
self.assertEqual(translated_code_2.code, FRAME_2.f_code)
|
|
self.assertEqual(ctx.translate_count, 1)
|
|
|
|
@patch(
|
|
"paddle.jit.sot.opcode_translator.executor.executor_cache.start_translate",
|
|
mock_start_translate,
|
|
)
|
|
def test_cache_miss_due_to_unknown_code(self):
|
|
with test_instruction_translator_cache_context() as ctx:
|
|
translated_code_1 = OpcodeExecutorCache()(FRAME_1)
|
|
assert translated_code_1 is not None
|
|
self.assertEqual(translated_code_1.code, FRAME_2.f_code)
|
|
self.assertEqual(ctx.translate_count, 1)
|
|
# cache miss
|
|
translated_code_2 = OpcodeExecutorCache()(FRAME_3)
|
|
assert translated_code_2 is not None
|
|
self.assertEqual(translated_code_2.code, FRAME_4.f_code)
|
|
self.assertEqual(ctx.translate_count, 2)
|
|
|
|
@patch(
|
|
"paddle.jit.sot.opcode_translator.executor.executor_cache.start_translate",
|
|
mock_start_translate,
|
|
)
|
|
def test_cache_miss_due_to_check_failed(self):
|
|
with test_instruction_translator_cache_context() as ctx:
|
|
translated_code_1 = OpcodeExecutorCache()(FRAME_3)
|
|
assert translated_code_1 is not None
|
|
self.assertEqual(translated_code_1.code, FRAME_4.f_code)
|
|
self.assertEqual(ctx.translate_count, 1)
|
|
# cache miss
|
|
translated_code_2 = OpcodeExecutorCache()(FRAME_3)
|
|
assert translated_code_2 is not None
|
|
self.assertEqual(translated_code_2.code, FRAME_4.f_code)
|
|
self.assertEqual(ctx.translate_count, 2)
|
|
|
|
|
|
def foo(x):
|
|
return x + 1
|
|
|
|
|
|
class TestCacheExceedLimit(TestCaseBase):
|
|
def test_cache_exceed_limit(self):
|
|
for _ in range(30):
|
|
input = random.random()
|
|
self.assert_results(foo, input)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|