-
-
Notifications
You must be signed in to change notification settings - Fork 34.4k
gh-146031: Allow keeping specialization enabled when specifying eval frame function #146032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2870,6 +2870,90 @@ def func(): | |
| self.do_test(func, names) | ||
|
|
||
|
|
||
| class Test_Pep523AllowSpecialization(unittest.TestCase): | ||
| """Tests for _PyInterpreterState_SetEvalFrameFunc with | ||
| allow_specialization=1.""" | ||
|
|
||
| def test_is_specialization_enabled_default(self): | ||
| # With no custom eval frame, specialization should be enabled | ||
| self.assertTrue(_testinternalcapi.is_specialization_enabled()) | ||
|
|
||
| def test_is_specialization_enabled_with_eval_frame(self): | ||
| # Setting eval frame with allow_specialization=0 disables specialization | ||
| try: | ||
| _testinternalcapi.set_eval_frame_record([]) | ||
| self.assertFalse(_testinternalcapi.is_specialization_enabled()) | ||
| finally: | ||
| _testinternalcapi.set_eval_frame_default() | ||
|
|
||
| def test_is_specialization_enabled_after_restore(self): | ||
| # Restoring the default eval frame re-enables specialization | ||
| try: | ||
| _testinternalcapi.set_eval_frame_record([]) | ||
| self.assertFalse(_testinternalcapi.is_specialization_enabled()) | ||
| finally: | ||
| _testinternalcapi.set_eval_frame_default() | ||
| self.assertTrue(_testinternalcapi.is_specialization_enabled()) | ||
|
|
||
| def test_is_specialization_enabled_with_allow(self): | ||
| # Setting eval frame with allow_specialization=1 keeps it enabled | ||
| try: | ||
| _testinternalcapi.set_eval_frame_interp([]) | ||
| self.assertTrue(_testinternalcapi.is_specialization_enabled()) | ||
| finally: | ||
| _testinternalcapi.set_eval_frame_default() | ||
|
|
||
| def test_allow_specialization_call(self): | ||
| def func(): | ||
| pass | ||
|
|
||
| def func_outer(): | ||
| func() | ||
|
|
||
| actual_calls = [] | ||
| try: | ||
| _testinternalcapi.set_eval_frame_interp( | ||
| actual_calls) | ||
| for i in range(SUFFICIENT_TO_DEOPT_AND_SPECIALIZE * 2): | ||
| func_outer() | ||
| finally: | ||
| _testinternalcapi.set_eval_frame_default() | ||
|
|
||
| # With specialization enabled, calls to inner() will dispatch | ||
| # through the installed frame evaluator | ||
| func_calls = [c for c in actual_calls if c == "func"] | ||
| self.assertEqual(len(func_calls), 0) | ||
|
|
||
| # But the normal interpreter loop still shouldn't be inlining things | ||
| func_outer_calls = [c for c in actual_calls if c == "func_outer"] | ||
| self.assertNotEqual(len(func_outer_calls), 0) | ||
|
Comment on lines
+2924
to
+2929
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is better to use the |
||
|
|
||
| def test_no_specialization_call(self): | ||
| # Without allow_specialization, ALL calls go through the eval frame. | ||
| # This is the existing PEP 523 behavior. | ||
| def inner(x=42): | ||
| pass | ||
| def func(): | ||
| inner() | ||
|
|
||
| # Pre-specialize | ||
| for _ in range(SUFFICIENT_TO_DEOPT_AND_SPECIALIZE): | ||
| func() | ||
|
|
||
| actual_calls = [] | ||
| try: | ||
| _testinternalcapi.set_eval_frame_record(actual_calls) | ||
| for _ in range(SUFFICIENT_TO_DEOPT_AND_SPECIALIZE): | ||
| func() | ||
| finally: | ||
| _testinternalcapi.set_eval_frame_default() | ||
|
|
||
| # Without allow_specialization, every call including inner() goes | ||
| # through the eval frame | ||
| expected = ["func", "inner"] * SUFFICIENT_TO_DEOPT_AND_SPECIALIZE | ||
| self.assertEqual(actual_calls, expected) | ||
|
|
||
|
|
||
| @unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED') | ||
| class TestPyThreadId(unittest.TestCase): | ||
| def test_py_thread_id(self): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| The unstable API _PyInterpreterState_SetEvalFrameFunc takes an additional option to specify if specialization should be allowed. When this option is set to 1 the specializer will turn Python -> Python calls into specialized opcodes and will execute the Python function in the current interpreter loop instead of calling to the frame evaluator. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -220,14 +220,15 @@ do { \ | |
| DISPATCH_GOTO_NON_TRACING(); \ | ||
| } | ||
|
|
||
| #define DISPATCH_INLINED(NEW_FRAME) \ | ||
| do { \ | ||
| assert(tstate->interp->eval_frame == NULL); \ | ||
| _PyFrame_SetStackPointer(frame, stack_pointer); \ | ||
| assert((NEW_FRAME)->previous == frame); \ | ||
| frame = tstate->current_frame = (NEW_FRAME); \ | ||
| CALL_STAT_INC(inlined_py_calls); \ | ||
| JUMP_TO_LABEL(start_frame); \ | ||
| #define DISPATCH_INLINED(NEW_FRAME) \ | ||
| do { \ | ||
| assert(tstate->interp->eval_frame == NULL || \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use your |
||
| tstate->interp->eval_frame_allow_specialization); \ | ||
| _PyFrame_SetStackPointer(frame, stack_pointer); \ | ||
| assert((NEW_FRAME)->previous == frame); \ | ||
| frame = tstate->current_frame = (NEW_FRAME); \ | ||
| CALL_STAT_INC(inlined_py_calls); \ | ||
| JUMP_TO_LABEL(start_frame); \ | ||
| } while (0) | ||
|
|
||
| /* Tuple access macros */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to change this API again. It break PyTorch every time we do so.
Maybe add another function to set the flag.