Skip to content

Commit 54ab316

Browse files
p-wysockialmiloszCopilot
authored
[PyOV] Add technical guide on how to debug PyAPI (openvinotoolkit#33284)
This doc scopes Linux debugging. Windows will come in a separate PR as it's much more complicated. ### Tickets: - CVS-159139 --------- Signed-off-by: p-wysocki <przemyslaw.wysocki@intel.com> Co-authored-by: Alicja Miloszewska <alicja.miloszewska@intel.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0aceb9b commit 54ab316

4 files changed

Lines changed: 235 additions & 0 deletions

File tree

src/bindings/python/docs/build.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ For details please refer to [Python requirements and version constraints managem
8080
Follow instructions in [How to test OpenVINO™ Python API?](./test_examples.md#Running_OpenVINO™_Python_API_tests) to verify the build.
8181

8282
## See also
83+
* [Debugging OpenVINO™ Python API with C++ breakpoints](./debugging_python_api.md)
8384
* [OpenVINO™ README](../../../../README.md)
8485
* [OpenVINO™ bindings README](../../README.md)
8586
* [Developer documentation](../../../../docs/dev/index.md)

src/bindings/python/docs/contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ XXXX, YYYY <-- only numbers from tickets
6565
```
6666

6767
## See also
68+
* [Debugging OpenVINO™ Python API with C++ breakpoints](./debugging_python_api.md)
6869
* [OpenVINO™ README](../../../../README.md)
6970
* [OpenVINO™ bindings README](../../README.md)
7071
* [Developer documentation](../../../../docs/dev/index.md)
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# Debugging OpenVINO Python API with C++ Breakpoints
2+
3+
This guide explains how to debug the OpenVINO Python API by setting breakpoints in the underlying C++ code. Since the Python API is implemented using pybind11 bindings over C++ code, debugging requires a special setup to step through both Python and C++ layers.
4+
5+
## Prerequisites
6+
7+
### Build OpenVINO with Debug Symbols
8+
9+
To debug C++ code, you need to build OpenVINO with debug symbols enabled. Add the following CMake flags:
10+
11+
```bash
12+
cmake -DCMAKE_BUILD_TYPE=Debug \
13+
-DENABLE_PYTHON=ON \
14+
...
15+
```
16+
17+
For complete build instructions, refer to [Building the OpenVINO Python API](./build.md).
18+
19+
### Required Tools
20+
21+
- GDB (GNU Debugger) - usually pre-installed on Linux systems
22+
- Python with debug symbols (recommended for better debugging experience)
23+
- VS Code with C/C++ extension (or any other debugger that supports C++ debugging)
24+
25+
### Install Test Dependencies
26+
27+
```bash
28+
pip install -r src/bindings/python/requirements_test.txt
29+
```
30+
31+
## Understanding the Python-C++ Bridge
32+
33+
The OpenVINO Python API is a thin wrapper around C++ implementation. When you call a Python method like `tensor.get_size()`, the execution flow is:
34+
35+
1. Python code calls the method
36+
2. pybind11 directs the call to C++
37+
3. C++ implementation in `ov::Tensor::get_size()` executes
38+
4. Result is directed back to Python
39+
40+
To debug this, we need to:
41+
- Start Python as the main process
42+
- Attach GDB to debug the C++ code
43+
- Set breakpoints in C++ source files
44+
45+
## Debugging Setup in VS Code
46+
47+
### 1. Create or Update `.vscode/launch.json`
48+
49+
Create a launch configuration in your workspace's `.vscode/launch.json`:
50+
51+
```json
52+
{
53+
"version": "0.2.0",
54+
"configurations": [
55+
{
56+
"name": "Python API Debug",
57+
"type": "cppdbg",
58+
"request": "launch",
59+
"program": "/path/to/your/python",
60+
"args": [
61+
"-m", "pytest",
62+
"tests/test_runtime/test_tensor.py::test_tensor_from_numpy",
63+
"-v"
64+
],
65+
"stopAtEntry": false,
66+
"cwd": "${workspaceFolder}/src/bindings/python/tests",
67+
"environment": [
68+
{
69+
"name": "PYTHONPATH",
70+
"value": "${workspaceFolder}/bin/intel64/Debug/python"
71+
},
72+
{
73+
"name": "LD_LIBRARY_PATH",
74+
"value": "${workspaceFolder}/bin/intel64/Debug"
75+
}
76+
],
77+
"MIMode": "gdb",
78+
"setupCommands": [
79+
{
80+
"description": "Enable pretty-printing for gdb",
81+
"text": "-enable-pretty-printing",
82+
"ignoreFailures": true
83+
}
84+
]
85+
}
86+
]
87+
}
88+
```
89+
90+
### 2. Configuration Parameters Explained
91+
92+
- **`program`**: Path to your Python interpreter. Use `which python` or `pyenv which python` to find it.
93+
- **`args`**: Arguments passed to Python. Here we're running pytest with a specific test.
94+
- **`cwd`**: Working directory - set to the tests directory.
95+
- **`PYTHONPATH`**: Points to the built Python bindings.
96+
- **`LD_LIBRARY_PATH`**: Points to the built OpenVINO libraries.
97+
98+
### 3. Adjust Paths for Your Environment
99+
100+
Replace the following in the configuration:
101+
102+
- `/path/to/your/python` → Your Python executable path
103+
- For pyenv: `~/.pyenv/versions/<env_name>/bin/python`
104+
- For system Python: `/usr/bin/python3`
105+
- `${workspaceFolder}` resolves to your OpenVINO repository root
106+
107+
## Step-by-Step Debugging Example
108+
109+
Let's debug the `ov::Tensor::get_size()` method, which is a stable API unlikely to be removed.
110+
111+
### 1. Set a Breakpoint in C++ Code
112+
113+
Open the C++ source file:
114+
```
115+
src/bindings/python/src/pyopenvino/core/tensor.cpp
116+
```
117+
118+
Find the `get_size` method binding:
119+
120+
```cpp
121+
cls.def("get_size",
122+
&ov::Tensor::get_size,
123+
R"(
124+
Gets Tensor's size as total number of elements.
125+
126+
:rtype: int
127+
)");
128+
```
129+
130+
The actual implementation is in the core library. Set a breakpoint at line (`&ov::Tensor::get_size`), or better yet, find the implementation in:
131+
132+
```
133+
src/core/include/openvino/core/tensor.hpp
134+
```
135+
136+
And set a breakpoint in the `get_size()` method implementation.
137+
138+
### 2. Prepare a Test Case
139+
140+
You can use an existing test that calls `get_size()`. For example, `tests/test_runtime/test_tensor.py` contains:
141+
142+
```python
143+
def test_tensor_from_numpy():
144+
arr = np.array([1, 2, 3])
145+
ov_tensor = ov.Tensor(arr)
146+
assert ov_tensor.get_size() == arr.size # This calls C++ code
147+
```
148+
149+
### 3. Update Launch Configuration
150+
151+
Modify the `args` in your launch configuration to run this specific test:
152+
153+
```json
154+
"args": [
155+
"-m", "pytest",
156+
"tests/test_runtime/test_tensor.py::test_tensor_from_numpy",
157+
"-v", "-s"
158+
]
159+
```
160+
161+
The `-s` flag disables output capturing, making debugging easier.
162+
163+
### 4. Start Debugging
164+
165+
1. Open the C++ file where you want to set a breakpoint
166+
2. Click in the left margin next to the line number to set a breakpoint
167+
3. Press `F5` or click "Run → Start Debugging"
168+
4. Select "Python API Debug" configuration
169+
5. The debugger will start Python, load the bindings, and stop at your C++ breakpoint
170+
171+
### 5. Debug as Normal
172+
173+
Once stopped at the breakpoint, you can:
174+
175+
- **Step through code**
176+
- **Inspect variables**: Hover over variables or use the Variables pane
177+
- **Evaluate expressions**: Use the Debug Console
178+
- **View call stack**: See both Python and C++ frames in the Call Stack pane
179+
180+
### Debugging Without Tests
181+
182+
You can also debug a standalone Python script:
183+
184+
```json
185+
{
186+
"name": "Python Script Debug",
187+
"type": "cppdbg",
188+
"request": "launch",
189+
"program": "/path/to/python",
190+
"args": ["${workspaceFolder}/my_script.py"],
191+
"cwd": "${workspaceFolder}",
192+
"environment": [
193+
{
194+
"name": "PYTHONPATH",
195+
"value": "${workspaceFolder}/bin/intel64/Debug/python"
196+
}
197+
],
198+
"MIMode": "gdb"
199+
}
200+
```
201+
202+
**This method does not use or require `launch.json`.** It uses GDB directly from the command line as an alternative to the VS Code debugging setup.
203+
204+
### Attaching to Running Python Process
205+
You can attach GDB to an already running Python process:
206+
207+
1. Start your Python script with a pause:
208+
```python
209+
import os
210+
import time
211+
print(f"PID: {os.getpid()}")
212+
time.sleep(30) # Time to attach debugger
213+
```
214+
215+
2. In another terminal, attach GDB using the process ID:
216+
```bash
217+
gdb -p <PID>
218+
```
219+
220+
3. Set breakpoints and continue execution:
221+
```
222+
(gdb) break ov::Tensor::get_size
223+
(gdb) continue
224+
```
225+
226+
## See also
227+
* [Building the OpenVINO Python API](./build.md)
228+
* [How to test OpenVINO Python API?](./test_examples.md)
229+
* [Contributing to OpenVINO Python API](./contributing.md)
230+
* [OpenVINO README](../../../../README.md)
231+
* [OpenVINO bindings README](../../README.md)
232+
* [Developer documentation](../../../../docs/dev/index.md)

src/bindings/python/docs/test_examples.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ Notice that the test name is shared between cases. In a real-life pull request,
132132
* Re-use common parts of the code (like multiple lines that create helper object) and move them out to make tests easier to read.
133133

134134
## See also
135+
* [Debugging OpenVINO™ Python API with C++ breakpoints](./debugging_python_api.md)
135136
* [OpenVINO™ README](../../../../README.md)
136137
* [OpenVINO™ bindings README](../../README.md)
137138
* [Developer documentation](../../../../docs/dev/index.md)

0 commit comments

Comments
 (0)