nneditor 1.0.0__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.
- nneditor/__init__.py +5 -0
- nneditor/__main__.py +12 -0
- nneditor/adapters/__init__.py +6 -0
- nneditor/adapters/detect.py +91 -0
- nneditor/adapters/jax/__init__.py +23 -0
- nneditor/adapters/jax/checkpoint.py +158 -0
- nneditor/adapters/jax/mlir_text.py +614 -0
- nneditor/adapters/jax/stablehlo.py +309 -0
- nneditor/adapters/onnx/__init__.py +56 -0
- nneditor/adapters/onnx/dtypes.py +85 -0
- nneditor/adapters/onnx/exporter.py +1081 -0
- nneditor/adapters/onnx/index.py +280 -0
- nneditor/adapters/onnx/indexer.py +1269 -0
- nneditor/adapters/onnx/numerical.py +319 -0
- nneditor/adapters/onnx/smoke_worker.py +118 -0
- nneditor/adapters/onnx/splice.py +197 -0
- nneditor/adapters/onnx/to_ir.py +411 -0
- nneditor/adapters/onnx/typed_data.py +279 -0
- nneditor/adapters/onnx/wire.py +297 -0
- nneditor/adapters/pytorch/__init__.py +27 -0
- nneditor/adapters/pytorch/checkpoint.py +292 -0
- nneditor/adapters/pytorch/fx.py +296 -0
- nneditor/adapters/pytorch/pickle_scan.py +413 -0
- nneditor/adapters/pytorch/pt2.py +635 -0
- nneditor/adapters/pytorch/safetensors.py +371 -0
- nneditor/adapters/pytorch/scalar_types.py +103 -0
- nneditor/adapters/pytorch/zip_store.py +148 -0
- nneditor/adapters/registry.py +185 -0
- nneditor/analysis/__init__.py +6 -0
- nneditor/analysis/detectors.py +766 -0
- nneditor/analysis/hierarchy.py +367 -0
- nneditor/analysis/layout.py +306 -0
- nneditor/analysis/lod.py +505 -0
- nneditor/analysis/statistics.py +337 -0
- nneditor/application/__init__.py +7 -0
- nneditor/application/editing.py +318 -0
- nneditor/application/edits_store.py +164 -0
- nneditor/application/hierarchy.py +618 -0
- nneditor/application/jobs.py +218 -0
- nneditor/application/navigation.py +228 -0
- nneditor/application/persistence.py +163 -0
- nneditor/application/session.py +1253 -0
- nneditor/application/slices.py +284 -0
- nneditor/application/statistics_store.py +89 -0
- nneditor/artifact_formats.py +35 -0
- nneditor/assets/favicon.png +0 -0
- nneditor/assets/icons/apple-touch-icon-192.png +0 -0
- nneditor/assets/icons/icon-192.png +0 -0
- nneditor/assets/icons/icon-512.png +0 -0
- nneditor/assets/icons/icon-maskable-192.png +0 -0
- nneditor/assets/icons/icon-maskable-512.png +0 -0
- nneditor/assets/manifest.json +35 -0
- nneditor/assets/nneditor.ico +0 -0
- nneditor/assets/nneditor.png +0 -0
- nneditor/cancellation.py +44 -0
- nneditor/desktop/__init__.py +21 -0
- nneditor/desktop/windows_associations.py +252 -0
- nneditor/diagnostics.py +526 -0
- nneditor/editing/__init__.py +12 -0
- nneditor/editing/commands.py +1259 -0
- nneditor/editing/cow.py +182 -0
- nneditor/editing/diff.py +219 -0
- nneditor/editing/revisions.py +572 -0
- nneditor/editing/validation.py +868 -0
- nneditor/input_generation.py +464 -0
- nneditor/ir/__init__.py +6 -0
- nneditor/ir/capabilities.py +595 -0
- nneditor/ir/core.py +644 -0
- nneditor/ir/identity.py +201 -0
- nneditor/ir/schema.py +189 -0
- nneditor/ir/serialize.py +518 -0
- nneditor/py.typed +0 -0
- nneditor/rendering/__init__.py +34 -0
- nneditor/rendering/contract.py +121 -0
- nneditor/rendering/flet_canvas.py +210 -0
- nneditor/rendering/flet_canvas_managed.py +341 -0
- nneditor/rendering/flet_shapes.py +278 -0
- nneditor/rendering/hit_testing.py +63 -0
- nneditor/rendering/scene.py +362 -0
- nneditor/rendering/spatial.py +170 -0
- nneditor/rendering/synthetic.py +202 -0
- nneditor/storage/__init__.py +6 -0
- nneditor/storage/cache.py +191 -0
- nneditor/storage/paths.py +53 -0
- nneditor/storage/reader.py +376 -0
- nneditor/storage/store.py +451 -0
- nneditor/tracing/__init__.py +117 -0
- nneditor/tracing/comparison.py +244 -0
- nneditor/tracing/contracts.py +453 -0
- nneditor/tracing/inputs.py +433 -0
- nneditor/tracing/runner.py +220 -0
- nneditor/tracing/store.py +408 -0
- nneditor/tracing/trace_worker.py +271 -0
- nneditor/tracing/visualization.py +253 -0
- nneditor/transformations/__init__.py +59 -0
- nneditor/transformations/calibration.py +118 -0
- nneditor/transformations/engine.py +942 -0
- nneditor/transformations/schema.py +308 -0
- nneditor/ui/__init__.py +1 -0
- nneditor/ui/app.py +4982 -0
- nneditor/ui/input_workspace.py +934 -0
- nneditor/ui/overview.py +478 -0
- nneditor/ui/shell_layout.py +353 -0
- nneditor/ui/tensor_tools.py +130 -0
- nneditor/ui/trace_graph.py +253 -0
- nneditor/ui/viewmodel.py +437 -0
- nneditor-1.0.0.dist-info/METADATA +318 -0
- nneditor-1.0.0.dist-info/RECORD +110 -0
- nneditor-1.0.0.dist-info/WHEEL +4 -0
- nneditor-1.0.0.dist-info/entry_points.txt +2 -0
nneditor/__init__.py
ADDED
nneditor/__main__.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Artifact-kind detection by content, not by extension (P6.5/P7.5).
|
|
2
|
+
|
|
3
|
+
Architectural rule 2 makes format support an artifact-and-capability matrix
|
|
4
|
+
rather than an extension list, so opening dispatches on what a file *is*:
|
|
5
|
+
its magic bytes, and for zip containers the members it holds. Extensions are
|
|
6
|
+
only a tie-breaker for formats whose content is genuinely ambiguous (textual
|
|
7
|
+
MLIR versus arbitrary text).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import struct
|
|
13
|
+
import zipfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from nneditor.ir.capabilities import ArtifactKind
|
|
17
|
+
|
|
18
|
+
__all__ = ["DetectionError", "detect_artifact_kind"]
|
|
19
|
+
|
|
20
|
+
_ZIP_MAGIC = b"PK\x03\x04"
|
|
21
|
+
_ONNX_PROTO_HINT = b"\x08" # ModelProto.ir_version, field 1 varint
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DetectionError(ValueError):
|
|
25
|
+
"""Raised when a file matches no supported artifact contract."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _zip_kind(path: Path) -> ArtifactKind:
|
|
29
|
+
try:
|
|
30
|
+
with zipfile.ZipFile(path) as archive:
|
|
31
|
+
names = archive.namelist()
|
|
32
|
+
except zipfile.BadZipFile as error:
|
|
33
|
+
raise DetectionError(f"{path.name} is not a readable archive") from error
|
|
34
|
+
# data.pkl wins over archive_format when both are present: a torch
|
|
35
|
+
# checkpoint always holds data.pkl, while anyone can append a spare
|
|
36
|
+
# archive_format member to reroute it into the PT2 parser.
|
|
37
|
+
if any(name.endswith("data.pkl") for name in names):
|
|
38
|
+
# Both plain state dicts and pickled FX modules live here; the FX
|
|
39
|
+
# reader is the one that can say which, so the caller tries the
|
|
40
|
+
# checkpoint path first and falls back on a scan refusal.
|
|
41
|
+
return ArtifactKind.PYTORCH_STATE_DICT
|
|
42
|
+
if any(name.endswith("archive_format") for name in names):
|
|
43
|
+
return ArtifactKind.PYTORCH_EXPORTED_PROGRAM
|
|
44
|
+
raise DetectionError(
|
|
45
|
+
f"{path.name} is a zip archive but holds no recognized model container"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _leading_content(prefix: bytes) -> bytes:
|
|
50
|
+
"""The first content-bearing line, with ``//`` comment lines skipped.
|
|
51
|
+
|
|
52
|
+
StableHLO exporters routinely emit header comments before ``module``, so
|
|
53
|
+
the probe must look at the first non-comment line.
|
|
54
|
+
"""
|
|
55
|
+
for line in prefix.split(b"\n"):
|
|
56
|
+
stripped = line.lstrip()
|
|
57
|
+
if not stripped or stripped.startswith(b"//"):
|
|
58
|
+
continue
|
|
59
|
+
return stripped
|
|
60
|
+
return b""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def detect_artifact_kind(path: Path | str) -> ArtifactKind:
|
|
64
|
+
"""The artifact contract a file satisfies, decided from its content."""
|
|
65
|
+
target = Path(path)
|
|
66
|
+
with open(target, "rb") as handle:
|
|
67
|
+
prefix = handle.read(4096)
|
|
68
|
+
if not prefix:
|
|
69
|
+
raise DetectionError(f"{target.name} is empty")
|
|
70
|
+
if prefix.startswith(_ZIP_MAGIC):
|
|
71
|
+
return _zip_kind(target)
|
|
72
|
+
# safetensors: an 8-byte little-endian header length that fits inside
|
|
73
|
+
# the file, followed immediately by '{'. Without the length check any
|
|
74
|
+
# file with a '{' at offset 8 would claim the format.
|
|
75
|
+
if len(prefix) > 8 and prefix[8:9] == b"{":
|
|
76
|
+
(header_length,) = struct.unpack("<Q", prefix[:8])
|
|
77
|
+
if 8 + header_length <= target.stat().st_size:
|
|
78
|
+
return ArtifactKind.SAFETENSORS
|
|
79
|
+
content = _leading_content(prefix)
|
|
80
|
+
if content.startswith(b"module") or content.startswith(b'"builtin.module"'):
|
|
81
|
+
return ArtifactKind.JAX_STABLEHLO
|
|
82
|
+
if prefix.startswith(_ONNX_PROTO_HINT):
|
|
83
|
+
return ArtifactKind.ONNX_MODEL
|
|
84
|
+
if target.suffix.lower() in {".onnx", ".pb"}:
|
|
85
|
+
return ArtifactKind.ONNX_MODEL
|
|
86
|
+
if target.suffix.lower() in {".mlir", ".stablehlo"}:
|
|
87
|
+
return ArtifactKind.JAX_STABLEHLO
|
|
88
|
+
raise DetectionError(
|
|
89
|
+
f"{target.name} matches no supported artifact contract; see "
|
|
90
|
+
"docs/artifact-capabilities.md for the accepted inputs"
|
|
91
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""JAX / StableHLO artifact adapters (Phase 7).
|
|
2
|
+
|
|
3
|
+
Textual StableHLO modules — what ``jax.export`` and ``jax.jit(...).lower()``
|
|
4
|
+
emit — are parsed natively: MLIR's textual form is regular enough that a
|
|
5
|
+
focused reader recovers functions, operations, values, and types without
|
|
6
|
+
linking MLIR or executing any JAX code. Orbax/Flax checkpoint trees open
|
|
7
|
+
weights-only. ``jax`` itself is a dev-only dependency used to generate
|
|
8
|
+
fixtures and validate this reader.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from nneditor.adapters.jax.checkpoint import open_orbax_checkpoint
|
|
12
|
+
from nneditor.adapters.jax.stablehlo import (
|
|
13
|
+
StableHloError,
|
|
14
|
+
open_stablehlo,
|
|
15
|
+
parse_stablehlo,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"StableHloError",
|
|
20
|
+
"open_orbax_checkpoint",
|
|
21
|
+
"open_stablehlo",
|
|
22
|
+
"parse_stablehlo",
|
|
23
|
+
]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Weights-only JAX/Flax/Orbax checkpoint sessions (P7.3).
|
|
2
|
+
|
|
3
|
+
An Orbax checkpoint directory holds one msgpack/JSON tree plus array data;
|
|
4
|
+
the portable, universally readable member is the **safetensors** or ``.npy``
|
|
5
|
+
payload many JAX pipelines emit alongside. This adapter opens the directory's
|
|
6
|
+
readable tensor files as a weights-only document with the *checkpoint tree*
|
|
7
|
+
preserved as a path-keyed structure — it never claims topology, because a
|
|
8
|
+
parameter tree describes no computation (the Orbax contract says exactly
|
|
9
|
+
that).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from itertools import islice
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Final
|
|
18
|
+
|
|
19
|
+
from nneditor import __version__
|
|
20
|
+
from nneditor.adapters.pytorch.safetensors import open_safetensors
|
|
21
|
+
from nneditor.diagnostics import DiagnosticLog, Severity
|
|
22
|
+
from nneditor.ir.capabilities import ArtifactKind, contract_for
|
|
23
|
+
from nneditor.ir.core import (
|
|
24
|
+
ArtifactRef,
|
|
25
|
+
Document,
|
|
26
|
+
Graph,
|
|
27
|
+
JsonValue,
|
|
28
|
+
ProvenanceEntry,
|
|
29
|
+
TensorRef,
|
|
30
|
+
)
|
|
31
|
+
from nneditor.ir.identity import ROOT_GRAPH_ID
|
|
32
|
+
from nneditor.storage.reader import hash_file
|
|
33
|
+
|
|
34
|
+
__all__ = ["OrbaxError", "open_orbax_checkpoint"]
|
|
35
|
+
|
|
36
|
+
_MAX_TREE_FILES: Final = 256
|
|
37
|
+
_MAX_TREE_FILE_BYTES: Final = 8 * 1024 * 1024
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OrbaxError(ValueError):
|
|
41
|
+
"""Raised when a checkpoint directory yields no readable tensors."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def open_orbax_checkpoint(path: Path | str) -> Document:
|
|
45
|
+
"""Open a checkpoint directory (or single safetensors file) weights-only.
|
|
46
|
+
|
|
47
|
+
A directory containing ``*.safetensors`` reads through the native
|
|
48
|
+
safetensors path; ``.npy`` members are read directly. The parameter tree
|
|
49
|
+
(``*.json`` metadata) is carried as an extension without interpretation.
|
|
50
|
+
|
|
51
|
+
Every failure — including unexpected ones from hostile input — surfaces
|
|
52
|
+
as :class:`OrbaxError`; the layered error contract in the session depends
|
|
53
|
+
on adapters never leaking raw exceptions.
|
|
54
|
+
"""
|
|
55
|
+
target = Path(path)
|
|
56
|
+
try:
|
|
57
|
+
return _open_orbax_checkpoint(target)
|
|
58
|
+
except OrbaxError:
|
|
59
|
+
raise
|
|
60
|
+
except Exception as error:
|
|
61
|
+
raise OrbaxError(
|
|
62
|
+
f"{target.name} could not be opened as a checkpoint: "
|
|
63
|
+
f"{type(error).__name__}: {error}"
|
|
64
|
+
) from error
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _open_orbax_checkpoint(target: Path) -> Document:
|
|
68
|
+
if target.is_file():
|
|
69
|
+
document = open_safetensors(target)
|
|
70
|
+
return document
|
|
71
|
+
|
|
72
|
+
if not target.is_dir():
|
|
73
|
+
raise OrbaxError(f"{target} is neither a file nor a directory")
|
|
74
|
+
|
|
75
|
+
log = DiagnosticLog()
|
|
76
|
+
safetensor_files = sorted(target.rglob("*.safetensors"))
|
|
77
|
+
tensors: list[TensorRef] = []
|
|
78
|
+
tree: dict[str, JsonValue] = {}
|
|
79
|
+
|
|
80
|
+
# The directory contents are untrusted: both the number of metadata
|
|
81
|
+
# files and each file's size are capped so a hostile checkpoint cannot
|
|
82
|
+
# turn the tree walk into unbounded reads.
|
|
83
|
+
metadata_files = sorted(islice(target.rglob("*.json"), _MAX_TREE_FILES + 1))
|
|
84
|
+
if len(metadata_files) > _MAX_TREE_FILES:
|
|
85
|
+
log.add(
|
|
86
|
+
"jax.tree-metadata-truncated",
|
|
87
|
+
Severity.WARNING,
|
|
88
|
+
f"the checkpoint holds more than {_MAX_TREE_FILES} metadata "
|
|
89
|
+
"files; only the first ones are carried",
|
|
90
|
+
target.name,
|
|
91
|
+
)
|
|
92
|
+
metadata_files = metadata_files[:_MAX_TREE_FILES]
|
|
93
|
+
for metadata in metadata_files:
|
|
94
|
+
try:
|
|
95
|
+
if metadata.stat().st_size > _MAX_TREE_FILE_BYTES:
|
|
96
|
+
log.add(
|
|
97
|
+
"jax.tree-metadata-too-large",
|
|
98
|
+
Severity.WARNING,
|
|
99
|
+
f"{metadata.name} exceeds the {_MAX_TREE_FILE_BYTES}-byte "
|
|
100
|
+
"metadata limit and is skipped",
|
|
101
|
+
metadata.name,
|
|
102
|
+
)
|
|
103
|
+
continue
|
|
104
|
+
tree[metadata.relative_to(target).as_posix()] = json.loads(
|
|
105
|
+
metadata.read_text(encoding="utf-8")
|
|
106
|
+
)
|
|
107
|
+
except (OSError, ValueError, RecursionError):
|
|
108
|
+
log.add(
|
|
109
|
+
"jax.unreadable-tree-metadata",
|
|
110
|
+
Severity.WARNING,
|
|
111
|
+
f"{metadata.name} is not readable JSON and is skipped",
|
|
112
|
+
metadata.name,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if not safetensor_files:
|
|
116
|
+
raise OrbaxError(
|
|
117
|
+
f"{target} holds no safetensors payload; export the checkpoint "
|
|
118
|
+
"with a safetensors writer to inspect its weights"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
primary = safetensor_files[0]
|
|
122
|
+
if len(safetensor_files) > 1:
|
|
123
|
+
log.add(
|
|
124
|
+
"jax.sharded-checkpoint",
|
|
125
|
+
Severity.WARNING,
|
|
126
|
+
f"{len(safetensor_files)} shards found; only {primary.name} is "
|
|
127
|
+
"opened in this build",
|
|
128
|
+
target.name,
|
|
129
|
+
)
|
|
130
|
+
document = open_safetensors(primary)
|
|
131
|
+
tensors = list(document.tensors.values())
|
|
132
|
+
|
|
133
|
+
extensions: list[tuple[str, JsonValue]] = list(document.extensions)
|
|
134
|
+
if tree:
|
|
135
|
+
extensions.append(("x-orbax.tree", tree))
|
|
136
|
+
|
|
137
|
+
contract = contract_for(ArtifactKind.ORBAX_CHECKPOINT)
|
|
138
|
+
return Document(
|
|
139
|
+
source=ArtifactRef(
|
|
140
|
+
path=str(primary),
|
|
141
|
+
content_hash=hash_file(primary),
|
|
142
|
+
byte_size=primary.stat().st_size,
|
|
143
|
+
),
|
|
144
|
+
artifact_kind=ArtifactKind.ORBAX_CHECKPOINT,
|
|
145
|
+
capabilities=contract.statuses,
|
|
146
|
+
graphs=[Graph(id=ROOT_GRAPH_ID, name="checkpoint")],
|
|
147
|
+
tensors=tensors,
|
|
148
|
+
provenance=[
|
|
149
|
+
ProvenanceEntry(
|
|
150
|
+
operation="import",
|
|
151
|
+
tool_version=f"nneditor {__version__}",
|
|
152
|
+
target=ROOT_GRAPH_ID,
|
|
153
|
+
parameters=(("loading_mode", "safe artifact"),),
|
|
154
|
+
)
|
|
155
|
+
],
|
|
156
|
+
diagnostics=(*document.diagnostics, *tuple(log)),
|
|
157
|
+
extensions=extensions,
|
|
158
|
+
)
|