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.
Files changed (110) hide show
  1. nneditor/__init__.py +5 -0
  2. nneditor/__main__.py +12 -0
  3. nneditor/adapters/__init__.py +6 -0
  4. nneditor/adapters/detect.py +91 -0
  5. nneditor/adapters/jax/__init__.py +23 -0
  6. nneditor/adapters/jax/checkpoint.py +158 -0
  7. nneditor/adapters/jax/mlir_text.py +614 -0
  8. nneditor/adapters/jax/stablehlo.py +309 -0
  9. nneditor/adapters/onnx/__init__.py +56 -0
  10. nneditor/adapters/onnx/dtypes.py +85 -0
  11. nneditor/adapters/onnx/exporter.py +1081 -0
  12. nneditor/adapters/onnx/index.py +280 -0
  13. nneditor/adapters/onnx/indexer.py +1269 -0
  14. nneditor/adapters/onnx/numerical.py +319 -0
  15. nneditor/adapters/onnx/smoke_worker.py +118 -0
  16. nneditor/adapters/onnx/splice.py +197 -0
  17. nneditor/adapters/onnx/to_ir.py +411 -0
  18. nneditor/adapters/onnx/typed_data.py +279 -0
  19. nneditor/adapters/onnx/wire.py +297 -0
  20. nneditor/adapters/pytorch/__init__.py +27 -0
  21. nneditor/adapters/pytorch/checkpoint.py +292 -0
  22. nneditor/adapters/pytorch/fx.py +296 -0
  23. nneditor/adapters/pytorch/pickle_scan.py +413 -0
  24. nneditor/adapters/pytorch/pt2.py +635 -0
  25. nneditor/adapters/pytorch/safetensors.py +371 -0
  26. nneditor/adapters/pytorch/scalar_types.py +103 -0
  27. nneditor/adapters/pytorch/zip_store.py +148 -0
  28. nneditor/adapters/registry.py +185 -0
  29. nneditor/analysis/__init__.py +6 -0
  30. nneditor/analysis/detectors.py +766 -0
  31. nneditor/analysis/hierarchy.py +367 -0
  32. nneditor/analysis/layout.py +306 -0
  33. nneditor/analysis/lod.py +505 -0
  34. nneditor/analysis/statistics.py +337 -0
  35. nneditor/application/__init__.py +7 -0
  36. nneditor/application/editing.py +318 -0
  37. nneditor/application/edits_store.py +164 -0
  38. nneditor/application/hierarchy.py +618 -0
  39. nneditor/application/jobs.py +218 -0
  40. nneditor/application/navigation.py +228 -0
  41. nneditor/application/persistence.py +163 -0
  42. nneditor/application/session.py +1253 -0
  43. nneditor/application/slices.py +284 -0
  44. nneditor/application/statistics_store.py +89 -0
  45. nneditor/artifact_formats.py +35 -0
  46. nneditor/assets/favicon.png +0 -0
  47. nneditor/assets/icons/apple-touch-icon-192.png +0 -0
  48. nneditor/assets/icons/icon-192.png +0 -0
  49. nneditor/assets/icons/icon-512.png +0 -0
  50. nneditor/assets/icons/icon-maskable-192.png +0 -0
  51. nneditor/assets/icons/icon-maskable-512.png +0 -0
  52. nneditor/assets/manifest.json +35 -0
  53. nneditor/assets/nneditor.ico +0 -0
  54. nneditor/assets/nneditor.png +0 -0
  55. nneditor/cancellation.py +44 -0
  56. nneditor/desktop/__init__.py +21 -0
  57. nneditor/desktop/windows_associations.py +252 -0
  58. nneditor/diagnostics.py +526 -0
  59. nneditor/editing/__init__.py +12 -0
  60. nneditor/editing/commands.py +1259 -0
  61. nneditor/editing/cow.py +182 -0
  62. nneditor/editing/diff.py +219 -0
  63. nneditor/editing/revisions.py +572 -0
  64. nneditor/editing/validation.py +868 -0
  65. nneditor/input_generation.py +464 -0
  66. nneditor/ir/__init__.py +6 -0
  67. nneditor/ir/capabilities.py +595 -0
  68. nneditor/ir/core.py +644 -0
  69. nneditor/ir/identity.py +201 -0
  70. nneditor/ir/schema.py +189 -0
  71. nneditor/ir/serialize.py +518 -0
  72. nneditor/py.typed +0 -0
  73. nneditor/rendering/__init__.py +34 -0
  74. nneditor/rendering/contract.py +121 -0
  75. nneditor/rendering/flet_canvas.py +210 -0
  76. nneditor/rendering/flet_canvas_managed.py +341 -0
  77. nneditor/rendering/flet_shapes.py +278 -0
  78. nneditor/rendering/hit_testing.py +63 -0
  79. nneditor/rendering/scene.py +362 -0
  80. nneditor/rendering/spatial.py +170 -0
  81. nneditor/rendering/synthetic.py +202 -0
  82. nneditor/storage/__init__.py +6 -0
  83. nneditor/storage/cache.py +191 -0
  84. nneditor/storage/paths.py +53 -0
  85. nneditor/storage/reader.py +376 -0
  86. nneditor/storage/store.py +451 -0
  87. nneditor/tracing/__init__.py +117 -0
  88. nneditor/tracing/comparison.py +244 -0
  89. nneditor/tracing/contracts.py +453 -0
  90. nneditor/tracing/inputs.py +433 -0
  91. nneditor/tracing/runner.py +220 -0
  92. nneditor/tracing/store.py +408 -0
  93. nneditor/tracing/trace_worker.py +271 -0
  94. nneditor/tracing/visualization.py +253 -0
  95. nneditor/transformations/__init__.py +59 -0
  96. nneditor/transformations/calibration.py +118 -0
  97. nneditor/transformations/engine.py +942 -0
  98. nneditor/transformations/schema.py +308 -0
  99. nneditor/ui/__init__.py +1 -0
  100. nneditor/ui/app.py +4982 -0
  101. nneditor/ui/input_workspace.py +934 -0
  102. nneditor/ui/overview.py +478 -0
  103. nneditor/ui/shell_layout.py +353 -0
  104. nneditor/ui/tensor_tools.py +130 -0
  105. nneditor/ui/trace_graph.py +253 -0
  106. nneditor/ui/viewmodel.py +437 -0
  107. nneditor-1.0.0.dist-info/METADATA +318 -0
  108. nneditor-1.0.0.dist-info/RECORD +110 -0
  109. nneditor-1.0.0.dist-info/WHEEL +4 -0
  110. nneditor-1.0.0.dist-info/entry_points.txt +2 -0
nneditor/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """NNEditor package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "1.0.0"
nneditor/__main__.py ADDED
@@ -0,0 +1,12 @@
1
+ """Run NNEditor with ``python -m nneditor``."""
2
+
3
+ from nneditor.ui.app import cli
4
+
5
+
6
+ def main() -> None:
7
+ """Launch the Flet application."""
8
+ cli()
9
+
10
+
11
+ if __name__ == "__main__": # pragma: no cover - interpreter entry point
12
+ main()
@@ -0,0 +1,6 @@
1
+ """Source-format adapters.
2
+
3
+ Each adapter maps one artifact contract from :mod:`nneditor.ir.capabilities`
4
+ into the canonical IR, preserving source-specific data in extension namespaces
5
+ rather than discarding it.
6
+ """
@@ -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
+ )