kiwi-array-jupyter 0.2.47__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kiwi_array_jupyter-0.2.47.dist-info/METADATA +117 -0
- kiwi_array_jupyter-0.2.47.dist-info/RECORD +12 -0
- kiwi_array_jupyter-0.2.47.dist-info/WHEEL +5 -0
- kiwi_array_jupyter-0.2.47.dist-info/entry_points.txt +4 -0
- kiwi_array_jupyter-0.2.47.dist-info/top_level.txt +1 -0
- kiwi_array_jupyter_kernel/__init__.py +5 -0
- kiwi_array_jupyter_kernel/__main__.py +13 -0
- kiwi_array_jupyter_kernel/assets/kernel-logo.png +0 -0
- kiwi_array_jupyter_kernel/binding.py +77 -0
- kiwi_array_jupyter_kernel/execution.py +90 -0
- kiwi_array_jupyter_kernel/install.py +172 -0
- kiwi_array_jupyter_kernel/kernel.py +185 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kiwi-array-jupyter
|
|
3
|
+
Version: 0.2.47
|
|
4
|
+
Summary: Jupyter kernel for Kiwi.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: kiwi-array==0.2.47
|
|
8
|
+
Requires-Dist: ipykernel>=6.29
|
|
9
|
+
Requires-Dist: jupyter_client>=8.6
|
|
10
|
+
Requires-Dist: jupyter_core>=5.7
|
|
11
|
+
|
|
12
|
+
# Kiwi Jupyter Kernel
|
|
13
|
+
|
|
14
|
+
`kiwi-array-jupyter` is a Jupyter kernel for Kiwi that takes the direct-bridge
|
|
15
|
+
route:
|
|
16
|
+
|
|
17
|
+
- Jupyter runs a small Python `ipykernel` host
|
|
18
|
+
- the Python host loads the local Kiwi C ABI bridge with `ctypes`
|
|
19
|
+
- the bridge owns a persistent Kiwi `Session`
|
|
20
|
+
- the bridge and `Session` come from the Kiwi source checkout or the packaged wheel payload
|
|
21
|
+
|
|
22
|
+
This deliberately avoids the JSON REPL transport. The kernel boundary is:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Jupyter
|
|
26
|
+
-> ipykernel
|
|
27
|
+
-> ctypes
|
|
28
|
+
-> libkiwi_bridge
|
|
29
|
+
-> Kiwi Session
|
|
30
|
+
-> MLX
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What Is Here
|
|
34
|
+
|
|
35
|
+
- `build.zig`: small Zig regression target wired against [`../../src/kiwi_bridge.zig`](../../src/kiwi_bridge.zig)
|
|
36
|
+
- [`../../bridge/include/kiwi_bridge.h`](../../bridge/include/kiwi_bridge.h): shared C ABI for persistent Kiwi sessions
|
|
37
|
+
- [`../runtime/src/kiwi_array/bridge.py`](../runtime/src/kiwi_array/bridge.py): main-owned Python bridge wrapper consumed by this kernel
|
|
38
|
+
- `zig-src/tests.zig`: focused bridge regression tests
|
|
39
|
+
- `pyproject.toml`: editable Python package for the kernel host
|
|
40
|
+
- `src/kiwi_array_jupyter_kernel/binding.py`: thin re-export of the main-owned Python bridge wrapper
|
|
41
|
+
- `src/kiwi_array_jupyter_kernel/execution.py`: notebook cell execution helpers
|
|
42
|
+
- `src/kiwi_array_jupyter_kernel/kernel.py`: `ipykernel` integration
|
|
43
|
+
- `src/kiwi_array_jupyter_kernel/install.py`: bridge build plus kernelspec install helper
|
|
44
|
+
- `src/kiwi_array_jupyter_kernel/assets/kernel-logo.png`: vendored kernelspec logo source used for Jupyter icon files
|
|
45
|
+
|
|
46
|
+
## Current Scope
|
|
47
|
+
|
|
48
|
+
This first cut is intentionally narrow:
|
|
49
|
+
|
|
50
|
+
- execution is direct and stateful
|
|
51
|
+
- cells run line-by-line using the same blank-line and full-line-comment rules as script mode
|
|
52
|
+
- assignments suppress output
|
|
53
|
+
- echoed results surface in the notebook
|
|
54
|
+
- JSON strings that carry a Vega-Lite schema also surface as Vega-Lite MIME data
|
|
55
|
+
- completion and `inspect` cover the current built-in named functions
|
|
56
|
+
- delimiter-only `is_complete` distinguishes incomplete from clearly invalid input
|
|
57
|
+
|
|
58
|
+
It does not yet try to provide:
|
|
59
|
+
|
|
60
|
+
- general structured array display beyond `text/plain`
|
|
61
|
+
- parser-aware completeness checks
|
|
62
|
+
- full symbol completion from live session state
|
|
63
|
+
- a native Zig Jupyter wire-protocol implementation
|
|
64
|
+
|
|
65
|
+
## Build And Install
|
|
66
|
+
|
|
67
|
+
From the Kiwi repository root:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
cd python/jupyter
|
|
71
|
+
uv python install --managed-python 3.14.2
|
|
72
|
+
uv sync --managed-python --python 3.14.2 --group dev
|
|
73
|
+
uv run --managed-python python -m kiwi_array_jupyter_kernel.install --user
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
In a fresh public checkout, bootstrap MLX first from the Kiwi root with
|
|
77
|
+
`KIWI_MLX_BACKEND=cpu scripts/bootstrap_deps.sh` unless `.deps/mlx` and
|
|
78
|
+
`.deps/mlx-c` already exist.
|
|
79
|
+
|
|
80
|
+
The install helper builds the official bridge in `../../zig-out/lib` if needed.
|
|
81
|
+
The installed library is:
|
|
82
|
+
|
|
83
|
+
- `../../zig-out/lib/libkiwi_bridge.dylib` on macOS
|
|
84
|
+
- `../../zig-out/lib/libkiwi_bridge.so` on Linux
|
|
85
|
+
|
|
86
|
+
The install helper writes a kernelspec that launches:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
python -m kiwi_array_jupyter_kernel -f {connection_file}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When the bridge has already been built, the kernelspec also captures the
|
|
93
|
+
absolute `KIWI_BRIDGE_LIB` path so notebook startup does not depend on
|
|
94
|
+
accidentally finding the right local library at runtime.
|
|
95
|
+
|
|
96
|
+
## Tests
|
|
97
|
+
|
|
98
|
+
From this directory:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
uv run --managed-python pytest tests/test_kiwi_jupyter_kernel.py
|
|
102
|
+
zig build test
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
From the repository root, `make test-jupyter` runs the Python Jupyter kernel
|
|
106
|
+
tests when that target is available.
|
|
107
|
+
|
|
108
|
+
## Notes
|
|
109
|
+
|
|
110
|
+
- The pinned dev interpreter is the uv-managed CPython `3.14.2` patch release tracked in [`.python-version`](.python-version).
|
|
111
|
+
- The `dev` dependency group includes the Python test stack for the kernel host plus `jupyter_kernel_test` for end-to-end kernel validation work.
|
|
112
|
+
- The kernelspec logo is sourced from the vendored [`src/kiwi_array_jupyter_kernel/assets/kernel-logo.png`](src/kiwi_array_jupyter_kernel/assets/kernel-logo.png), derived from the current website favicon artwork instead of depending on generated website output.
|
|
113
|
+
- The loader looks for packaged wheel libraries first and then `../../zig-out/lib` in source checkouts.
|
|
114
|
+
- Set `KIWI_BRIDGE_LIB` to override the bridge path.
|
|
115
|
+
- `KIWI_JUPYTER_BRIDGE_LIB` is still accepted as a compatibility alias.
|
|
116
|
+
- Set `KIWI_MLX_PREFIX` and `KIWI_MLX_C_INCLUDE` to override the MLX link inputs when building the bridge from Python.
|
|
117
|
+
- Set `KIWI_JUPYTER_DEVICE=cpu` or `gpu` to pin the default MLX device.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
kiwi_array_jupyter_kernel/__init__.py,sha256=fiqyqv19NWBX0LOG-WYLhi4tB_eP3gfbiIfuFVJcx_0,86
|
|
2
|
+
kiwi_array_jupyter_kernel/__main__.py,sha256=Rexv_oz2WINXaK8I6hFLbVUvDRd43XrZHT-Pms__X2Y,231
|
|
3
|
+
kiwi_array_jupyter_kernel/binding.py,sha256=LQ9jwQLjcjhky03znbtOTmSf4gALNZbcH3f-REqvVbs,1678
|
|
4
|
+
kiwi_array_jupyter_kernel/execution.py,sha256=uS6qIb6dunKXU2KKghyG-Uhh408G1ejCJDQfkMCy0yw,2488
|
|
5
|
+
kiwi_array_jupyter_kernel/install.py,sha256=_kD76sjfZlSaHx_V5WcQrNFuKHgioAKfj-MDQj8Ltx4,5788
|
|
6
|
+
kiwi_array_jupyter_kernel/kernel.py,sha256=1EMOAerSPnqFtwp0rOcbLUK9FDoQ6pELV_-CblvKH3k,5695
|
|
7
|
+
kiwi_array_jupyter_kernel/assets/kernel-logo.png,sha256=xLMso0FjTO4slnlqWoMOsKdTzZ0KC8J7ktaSpk5maIM,37182
|
|
8
|
+
kiwi_array_jupyter-0.2.47.dist-info/METADATA,sha256=l7EmsJJ5W3NiomorkCL2qxGtMGvEG6yQWg49HsECA0Y,4676
|
|
9
|
+
kiwi_array_jupyter-0.2.47.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
kiwi_array_jupyter-0.2.47.dist-info/entry_points.txt,sha256=9iaCDCXiOxpbm_pcRZvzgMrq28wRiCeZ3Zu_eTO65rA,230
|
|
11
|
+
kiwi_array_jupyter-0.2.47.dist-info/top_level.txt,sha256=nbewsgAcmb3-Fcw3cptPjUhL237w2giyMn0R5BHYxDo,26
|
|
12
|
+
kiwi_array_jupyter-0.2.47.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kiwi_array_jupyter_kernel
|
|
Binary file
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
DEVICE_ENV_VAR = "KIWI_JUPYTER_DEVICE"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def jupyter_root() -> Path:
|
|
10
|
+
return Path(__file__).resolve().parents[2]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def python_root() -> Path:
|
|
14
|
+
return jupyter_root().parent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def kiwi_root() -> Path:
|
|
18
|
+
return python_root().parent
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def experiment_root() -> Path:
|
|
22
|
+
return jupyter_root()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import kiwi_array.bridge # noqa: F401
|
|
27
|
+
except ImportError:
|
|
28
|
+
_KIWI_BRIDGE_PYTHON_SRC = python_root() / "runtime" / "src"
|
|
29
|
+
if str(_KIWI_BRIDGE_PYTHON_SRC) not in sys.path:
|
|
30
|
+
sys.path.insert(0, str(_KIWI_BRIDGE_PYTHON_SRC))
|
|
31
|
+
|
|
32
|
+
from kiwi_array.bridge import ( # noqa: E402
|
|
33
|
+
AUTOGRAD_PATH_NAMES,
|
|
34
|
+
LEGACY_LIB_ENV_VAR,
|
|
35
|
+
LIB_ENV_VAR,
|
|
36
|
+
RUNTIME_ENV_VAR,
|
|
37
|
+
STATUS_NAMES,
|
|
38
|
+
KiwiBridgeError,
|
|
39
|
+
KiwiEvalResult,
|
|
40
|
+
KiwiSession,
|
|
41
|
+
active_runtime_descriptor,
|
|
42
|
+
build_bridge,
|
|
43
|
+
default_library_candidates,
|
|
44
|
+
discover_runtime_descriptors,
|
|
45
|
+
find_library_path,
|
|
46
|
+
implementation_root,
|
|
47
|
+
load_library,
|
|
48
|
+
platform_library_name,
|
|
49
|
+
runtime_library_env_var,
|
|
50
|
+
runtime_library_search_dir,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"AUTOGRAD_PATH_NAMES",
|
|
55
|
+
"DEVICE_ENV_VAR",
|
|
56
|
+
"LEGACY_LIB_ENV_VAR",
|
|
57
|
+
"LIB_ENV_VAR",
|
|
58
|
+
"RUNTIME_ENV_VAR",
|
|
59
|
+
"STATUS_NAMES",
|
|
60
|
+
"KiwiBridgeError",
|
|
61
|
+
"KiwiEvalResult",
|
|
62
|
+
"KiwiSession",
|
|
63
|
+
"active_runtime_descriptor",
|
|
64
|
+
"build_bridge",
|
|
65
|
+
"default_library_candidates",
|
|
66
|
+
"discover_runtime_descriptors",
|
|
67
|
+
"experiment_root",
|
|
68
|
+
"find_library_path",
|
|
69
|
+
"implementation_root",
|
|
70
|
+
"jupyter_root",
|
|
71
|
+
"kiwi_root",
|
|
72
|
+
"load_library",
|
|
73
|
+
"platform_library_name",
|
|
74
|
+
"python_root",
|
|
75
|
+
"runtime_library_env_var",
|
|
76
|
+
"runtime_library_search_dir",
|
|
77
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Iterator, Protocol
|
|
5
|
+
|
|
6
|
+
from .binding import KiwiEvalResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SessionProtocol(Protocol):
|
|
10
|
+
def eval(self, source: str) -> KiwiEvalResult:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class CellOutput:
|
|
16
|
+
line_no: int
|
|
17
|
+
text: str
|
|
18
|
+
autograd_path: str
|
|
19
|
+
display_mime: str | None = None
|
|
20
|
+
display_data: str | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CellExecutionError(RuntimeError):
|
|
24
|
+
def __init__(self, line_no: int, status: str) -> None:
|
|
25
|
+
self.line_no = line_no
|
|
26
|
+
self.status = status
|
|
27
|
+
super().__init__(f"line {line_no}: !{status}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def iter_executable_lines(code: str) -> Iterator[tuple[int, str]]:
|
|
31
|
+
for line_no, raw_line in enumerate(code.splitlines(), start=1):
|
|
32
|
+
line = raw_line.rstrip("\r")
|
|
33
|
+
trimmed = line.strip(" \t")
|
|
34
|
+
if not trimmed or trimmed.startswith("/"):
|
|
35
|
+
continue
|
|
36
|
+
yield line_no, line
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def execute_cell(session: SessionProtocol, code: str) -> list[CellOutput]:
|
|
40
|
+
outputs: list[CellOutput] = []
|
|
41
|
+
for line_no, line in iter_executable_lines(code):
|
|
42
|
+
result = session.eval(line)
|
|
43
|
+
if result.status != "ok":
|
|
44
|
+
raise CellExecutionError(line_no, result.status)
|
|
45
|
+
if result.echoed and result.text is not None:
|
|
46
|
+
outputs.append(
|
|
47
|
+
CellOutput(
|
|
48
|
+
line_no=line_no,
|
|
49
|
+
text=result.text,
|
|
50
|
+
autograd_path=result.autograd_path,
|
|
51
|
+
display_mime=result.display_mime,
|
|
52
|
+
display_data=result.display_data,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
return outputs
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def simple_is_complete(code: str) -> str:
|
|
59
|
+
stack: list[str] = []
|
|
60
|
+
in_string = False
|
|
61
|
+
pairs = {
|
|
62
|
+
"(": ")",
|
|
63
|
+
"[": "]",
|
|
64
|
+
"{": "}",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for raw_line in code.splitlines():
|
|
68
|
+
stripped = raw_line.lstrip(" \t")
|
|
69
|
+
if stripped.startswith("/"):
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
for ch in raw_line:
|
|
73
|
+
if ch == '"':
|
|
74
|
+
in_string = not in_string
|
|
75
|
+
continue
|
|
76
|
+
if in_string:
|
|
77
|
+
continue
|
|
78
|
+
if ch in "([{":
|
|
79
|
+
stack.append(ch)
|
|
80
|
+
continue
|
|
81
|
+
if ch in ")]}":
|
|
82
|
+
if not stack:
|
|
83
|
+
return "invalid"
|
|
84
|
+
opener = stack.pop()
|
|
85
|
+
if pairs[opener] != ch:
|
|
86
|
+
return "invalid"
|
|
87
|
+
|
|
88
|
+
if in_string or stack:
|
|
89
|
+
return "incomplete"
|
|
90
|
+
return "complete"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from importlib import resources
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional, Sequence
|
|
12
|
+
|
|
13
|
+
from .binding import (
|
|
14
|
+
DEVICE_ENV_VAR,
|
|
15
|
+
LIB_ENV_VAR,
|
|
16
|
+
build_bridge,
|
|
17
|
+
experiment_root,
|
|
18
|
+
find_library_path,
|
|
19
|
+
runtime_library_env_var,
|
|
20
|
+
runtime_library_search_dir,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
DEFAULT_KERNEL_NAME = "kiwi"
|
|
24
|
+
DEFAULT_DISPLAY_NAME = "Kiwi"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def default_logo_candidates() -> list[Path]:
|
|
28
|
+
return [
|
|
29
|
+
Path(__file__).resolve().parent / "assets" / "kernel-logo.png",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find_logo_path(path: Optional[str | Path] = None) -> Optional[Path]:
|
|
34
|
+
if path is not None:
|
|
35
|
+
candidate = Path(path).expanduser()
|
|
36
|
+
if candidate.exists():
|
|
37
|
+
return candidate
|
|
38
|
+
raise FileNotFoundError(f"Kiwi kernel logo not found at {candidate}")
|
|
39
|
+
|
|
40
|
+
for candidate in default_logo_candidates():
|
|
41
|
+
if candidate.exists():
|
|
42
|
+
return candidate
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_kernel_logos(dst_dir: Path, logo_path: Optional[str | Path] = None) -> None:
|
|
47
|
+
source = find_logo_path(logo_path)
|
|
48
|
+
if source is not None:
|
|
49
|
+
for name in ("logo-32x32.png", "logo-64x64.png"):
|
|
50
|
+
shutil.copy2(source, dst_dir / name)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
resource = resources.files("kiwi_array_jupyter_kernel") / "assets" / "kernel-logo.png"
|
|
54
|
+
with resources.as_file(resource) as resource_path:
|
|
55
|
+
for name in ("logo-32x32.png", "logo-64x64.png"):
|
|
56
|
+
shutil.copy2(resource_path, dst_dir / name)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def source_python_paths() -> list[Path]:
|
|
60
|
+
candidates = [
|
|
61
|
+
experiment_root() / "src",
|
|
62
|
+
experiment_root().parent / "runtime" / "src",
|
|
63
|
+
]
|
|
64
|
+
return [path for path in candidates if path.exists()]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_kernel_spec(
|
|
68
|
+
display_name: str = DEFAULT_DISPLAY_NAME,
|
|
69
|
+
device: str = "auto",
|
|
70
|
+
python_executable: Optional[str] = None,
|
|
71
|
+
library_path: Optional[str] = None,
|
|
72
|
+
python_path: Optional[Sequence[str | Path]] = None,
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
python_cmd = python_executable or sys.executable
|
|
75
|
+
env = {
|
|
76
|
+
DEVICE_ENV_VAR: device,
|
|
77
|
+
}
|
|
78
|
+
if python_path:
|
|
79
|
+
paths = [str(Path(path)) for path in python_path]
|
|
80
|
+
existing_python_path = os.environ.get("PYTHONPATH")
|
|
81
|
+
env["PYTHONPATH"] = (
|
|
82
|
+
os.pathsep.join([*paths, existing_python_path])
|
|
83
|
+
if existing_python_path
|
|
84
|
+
else os.pathsep.join(paths)
|
|
85
|
+
)
|
|
86
|
+
runtime_env_var = runtime_library_env_var()
|
|
87
|
+
if runtime_env_var is not None:
|
|
88
|
+
runtime_dir = runtime_library_search_dir()
|
|
89
|
+
existing = os.environ.get(runtime_env_var)
|
|
90
|
+
env[runtime_env_var] = (
|
|
91
|
+
os.pathsep.join([str(runtime_dir), existing]) if existing else str(runtime_dir)
|
|
92
|
+
)
|
|
93
|
+
if library_path is not None:
|
|
94
|
+
env[LIB_ENV_VAR] = library_path
|
|
95
|
+
return {
|
|
96
|
+
"argv": [python_cmd, "-m", "kiwi_array_jupyter_kernel", "-f", "{connection_file}"],
|
|
97
|
+
"display_name": display_name,
|
|
98
|
+
"language": "kiwi",
|
|
99
|
+
"env": env,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def install_kernel(
|
|
104
|
+
name: str = DEFAULT_KERNEL_NAME,
|
|
105
|
+
display_name: str = DEFAULT_DISPLAY_NAME,
|
|
106
|
+
device: str = "auto",
|
|
107
|
+
user: bool = True,
|
|
108
|
+
prefix: Optional[str] = None,
|
|
109
|
+
skip_build: bool = False,
|
|
110
|
+
optimize: str = "ReleaseFast",
|
|
111
|
+
) -> str:
|
|
112
|
+
if not skip_build:
|
|
113
|
+
build_bridge(optimize=optimize)
|
|
114
|
+
|
|
115
|
+
from jupyter_client.kernelspec import KernelSpecManager
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
library_path = str(find_library_path())
|
|
119
|
+
except FileNotFoundError:
|
|
120
|
+
library_path = None
|
|
121
|
+
|
|
122
|
+
spec = build_kernel_spec(
|
|
123
|
+
display_name=display_name,
|
|
124
|
+
device=device,
|
|
125
|
+
library_path=library_path,
|
|
126
|
+
python_path=source_python_paths(),
|
|
127
|
+
)
|
|
128
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
129
|
+
tmp_path = Path(tmpdir)
|
|
130
|
+
(tmp_path / "kernel.json").write_text(json.dumps(spec, indent=2) + "\n")
|
|
131
|
+
write_kernel_logos(tmp_path)
|
|
132
|
+
return KernelSpecManager().install_kernel_spec(tmpdir, kernel_name=name, user=user, prefix=prefix)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
|
|
136
|
+
parser = argparse.ArgumentParser(description="Install the Kiwi Jupyter kernel.")
|
|
137
|
+
parser.add_argument("--name", default=DEFAULT_KERNEL_NAME, help="Kernel name used by Jupyter.")
|
|
138
|
+
parser.add_argument("--display-name", default=DEFAULT_DISPLAY_NAME, help="Kernel display name shown in Jupyter.")
|
|
139
|
+
parser.add_argument("--device", choices=("auto", "cpu", "gpu"), default="auto", help="Default Kiwi device for the kernel.")
|
|
140
|
+
parser.add_argument("--optimize", choices=("Debug", "ReleaseFast", "ReleaseSafe", "ReleaseSmall"), default="ReleaseFast", help="Zig optimize mode used when building the bridge.")
|
|
141
|
+
parser.add_argument("--skip-build", action="store_true", help="Skip `zig build` before installing the kernelspec.")
|
|
142
|
+
parser.add_argument("--prefix", help="Install the kernelspec under the given prefix.")
|
|
143
|
+
parser.add_argument("--sys-prefix", action="store_true", help="Install the kernelspec into the current Python environment prefix.")
|
|
144
|
+
parser.add_argument("--user", action="store_true", help="Install the kernelspec into the user Jupyter data dir (default).")
|
|
145
|
+
return parser.parse_args(argv)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
149
|
+
args = parse_args(argv)
|
|
150
|
+
prefix = args.prefix
|
|
151
|
+
user = True
|
|
152
|
+
if args.sys_prefix:
|
|
153
|
+
prefix = sys.prefix
|
|
154
|
+
user = False
|
|
155
|
+
elif args.prefix is not None:
|
|
156
|
+
user = False
|
|
157
|
+
|
|
158
|
+
location = install_kernel(
|
|
159
|
+
name=args.name,
|
|
160
|
+
display_name=args.display_name,
|
|
161
|
+
device=args.device,
|
|
162
|
+
user=user,
|
|
163
|
+
prefix=prefix,
|
|
164
|
+
skip_build=args.skip_build,
|
|
165
|
+
optimize=args.optimize,
|
|
166
|
+
)
|
|
167
|
+
print(location)
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
if __name__ == "__main__":
|
|
172
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ipykernel.kernelbase import Kernel
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
from .binding import DEVICE_ENV_VAR, LIB_ENV_VAR, KiwiSession
|
|
12
|
+
from .execution import CellExecutionError, CellOutput, execute_cell, simple_is_complete
|
|
13
|
+
|
|
14
|
+
SYMBOL_DOCS = {
|
|
15
|
+
"exp": "exp[x] applies the elementwise exponential.",
|
|
16
|
+
"grad": (
|
|
17
|
+
"grad[f] returns a function that differentiates scalar-returning f "
|
|
18
|
+
"with respect to its first argument."
|
|
19
|
+
),
|
|
20
|
+
"log": "log[x] applies the elementwise natural logarithm.",
|
|
21
|
+
"sigmoid": "sigmoid[x] applies the elementwise logistic sigmoid.",
|
|
22
|
+
"tanh": "tanh[x] applies the elementwise hyperbolic tangent.",
|
|
23
|
+
"valuegrad": (
|
|
24
|
+
"valuegrad[f] returns a function that yields both the scalar result "
|
|
25
|
+
"of f and its gradient."
|
|
26
|
+
),
|
|
27
|
+
}
|
|
28
|
+
COMPLETION_WORDS = tuple(SYMBOL_DOCS)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class KiwiKernel(Kernel):
|
|
32
|
+
implementation = "kiwi-array-jupyter"
|
|
33
|
+
implementation_version = __version__
|
|
34
|
+
language = "kiwi"
|
|
35
|
+
language_version = "0.2"
|
|
36
|
+
banner = "Kiwi 0.2 kernel"
|
|
37
|
+
language_info = {
|
|
38
|
+
"name": "kiwi",
|
|
39
|
+
"mimetype": "text/x-kiwi",
|
|
40
|
+
"file_extension": ".k",
|
|
41
|
+
"pygments_lexer": "text",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
45
|
+
super().__init__(**kwargs)
|
|
46
|
+
device = os.environ.get(DEVICE_ENV_VAR, "auto")
|
|
47
|
+
library_path = os.environ.get(LIB_ENV_VAR)
|
|
48
|
+
self._session = KiwiSession(device=device, library_path=library_path)
|
|
49
|
+
|
|
50
|
+
def do_execute(
|
|
51
|
+
self,
|
|
52
|
+
code: str,
|
|
53
|
+
silent: bool,
|
|
54
|
+
store_history: bool = True,
|
|
55
|
+
user_expressions: Any = None,
|
|
56
|
+
allow_stdin: bool = False,
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
|
+
del store_history, allow_stdin
|
|
59
|
+
try:
|
|
60
|
+
outputs = execute_cell(self._session, code)
|
|
61
|
+
except CellExecutionError as exc:
|
|
62
|
+
if not silent:
|
|
63
|
+
self.send_response(
|
|
64
|
+
self.iopub_socket,
|
|
65
|
+
"error",
|
|
66
|
+
{
|
|
67
|
+
"ename": "KiwiError",
|
|
68
|
+
"evalue": str(exc),
|
|
69
|
+
"traceback": [],
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
return {
|
|
73
|
+
"status": "error",
|
|
74
|
+
"ename": "KiwiError",
|
|
75
|
+
"evalue": str(exc),
|
|
76
|
+
"traceback": [],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if not silent:
|
|
80
|
+
for output in outputs[:-1]:
|
|
81
|
+
self.send_response(
|
|
82
|
+
self.iopub_socket,
|
|
83
|
+
"stream",
|
|
84
|
+
{
|
|
85
|
+
"name": "stdout",
|
|
86
|
+
"text": output.text + "\n",
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if outputs:
|
|
91
|
+
last = outputs[-1]
|
|
92
|
+
self.send_response(
|
|
93
|
+
self.iopub_socket,
|
|
94
|
+
"execute_result",
|
|
95
|
+
{
|
|
96
|
+
"execution_count": self.execution_count,
|
|
97
|
+
"data": _display_data_for_output(last),
|
|
98
|
+
"metadata": {"kiwi_autograd_path": last.autograd_path},
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"status": "ok",
|
|
104
|
+
"execution_count": self.execution_count,
|
|
105
|
+
"payload": [],
|
|
106
|
+
"user_expressions": {},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def do_complete(self, code: str, cursor_pos: int) -> dict[str, Any]:
|
|
110
|
+
prefix = _completion_prefix(code[:cursor_pos])
|
|
111
|
+
matches = [word for word in COMPLETION_WORDS if prefix and word.startswith(prefix)]
|
|
112
|
+
return {
|
|
113
|
+
"matches": matches,
|
|
114
|
+
"cursor_start": cursor_pos - len(prefix),
|
|
115
|
+
"cursor_end": cursor_pos,
|
|
116
|
+
"metadata": {},
|
|
117
|
+
"status": "ok",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def do_inspect(
|
|
121
|
+
self,
|
|
122
|
+
code: str,
|
|
123
|
+
cursor_pos: int,
|
|
124
|
+
detail_level: int = 0,
|
|
125
|
+
omit_sections: Any = (),
|
|
126
|
+
) -> dict[str, Any]:
|
|
127
|
+
del detail_level, omit_sections
|
|
128
|
+
symbol = _symbol_at_cursor(code, cursor_pos)
|
|
129
|
+
if symbol is None:
|
|
130
|
+
return {
|
|
131
|
+
"status": "ok",
|
|
132
|
+
"found": False,
|
|
133
|
+
"data": {},
|
|
134
|
+
"metadata": {},
|
|
135
|
+
}
|
|
136
|
+
doc = SYMBOL_DOCS.get(symbol)
|
|
137
|
+
if doc is None:
|
|
138
|
+
return {
|
|
139
|
+
"status": "ok",
|
|
140
|
+
"found": False,
|
|
141
|
+
"data": {},
|
|
142
|
+
"metadata": {},
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
"status": "ok",
|
|
146
|
+
"found": True,
|
|
147
|
+
"data": {"text/plain": doc},
|
|
148
|
+
"metadata": {},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
def do_is_complete(self, code: str) -> dict[str, Any]:
|
|
152
|
+
status = simple_is_complete(code)
|
|
153
|
+
reply = {"status": status}
|
|
154
|
+
if status == "incomplete":
|
|
155
|
+
reply["indent"] = " "
|
|
156
|
+
return reply
|
|
157
|
+
|
|
158
|
+
def do_shutdown(self, restart: bool) -> dict[str, Any]:
|
|
159
|
+
self._session.close()
|
|
160
|
+
return {"status": "ok", "restart": restart}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _completion_prefix(code: str) -> str:
|
|
164
|
+
match = re.search(r"[A-Za-z_][A-Za-z0-9_]*$", code)
|
|
165
|
+
if match is None:
|
|
166
|
+
return ""
|
|
167
|
+
return match.group(0)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _symbol_at_cursor(code: str, cursor_pos: int) -> str | None:
|
|
171
|
+
for match in re.finditer(r"[A-Za-z_][A-Za-z0-9_]*", code):
|
|
172
|
+
if match.start() <= cursor_pos <= match.end():
|
|
173
|
+
return match.group(0)
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _display_data_for_output(output: CellOutput) -> dict[str, Any]:
|
|
178
|
+
data: dict[str, Any] = {"text/plain": output.text}
|
|
179
|
+
if output.display_mime is None or output.display_data is None:
|
|
180
|
+
return data
|
|
181
|
+
try:
|
|
182
|
+
data[output.display_mime] = json.loads(output.display_data)
|
|
183
|
+
except json.JSONDecodeError:
|
|
184
|
+
pass
|
|
185
|
+
return data
|