wsm 0.1.0__tar.gz
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.
- wsm-0.1.0/PKG-INFO +50 -0
- wsm-0.1.0/README.md +27 -0
- wsm-0.1.0/pyproject.toml +27 -0
- wsm-0.1.0/setup.cfg +4 -0
- wsm-0.1.0/src/wsm/__init__.py +14 -0
- wsm-0.1.0/src/wsm/__main__.py +3 -0
- wsm-0.1.0/src/wsm/_publication.py +19 -0
- wsm-0.1.0/src/wsm/cli.py +78 -0
- wsm-0.1.0/src/wsm/coding_tui.py +79 -0
- wsm-0.1.0/src/wsm/editing.py +394 -0
- wsm-0.1.0/src/wsm/errors.py +7 -0
- wsm-0.1.0/src/wsm/modal_trial.py +162 -0
- wsm-0.1.0/src/wsm/program.py +213 -0
- wsm-0.1.0/src/wsm/trial.py +248 -0
- wsm-0.1.0/src/wsm/trial_distillation.py +197 -0
- wsm-0.1.0/src/wsm/trial_editor.py +89 -0
- wsm-0.1.0/src/wsm/trial_grading.py +84 -0
- wsm-0.1.0/src/wsm/trial_poc.py +422 -0
- wsm-0.1.0/src/wsm/trial_training.py +261 -0
- wsm-0.1.0/src/wsm/trial_transport.py +188 -0
- wsm-0.1.0/src/wsm/trial_worker.py +81 -0
- wsm-0.1.0/src/wsm/tui.py +110 -0
- wsm-0.1.0/src/wsm.egg-info/PKG-INFO +50 -0
- wsm-0.1.0/src/wsm.egg-info/SOURCES.txt +38 -0
- wsm-0.1.0/src/wsm.egg-info/dependency_links.txt +1 -0
- wsm-0.1.0/src/wsm.egg-info/entry_points.txt +2 -0
- wsm-0.1.0/src/wsm.egg-info/requires.txt +18 -0
- wsm-0.1.0/src/wsm.egg-info/top_level.txt +1 -0
- wsm-0.1.0/tests/test_cli.py +34 -0
- wsm-0.1.0/tests/test_coding_tui.py +25 -0
- wsm-0.1.0/tests/test_editing.py +255 -0
- wsm-0.1.0/tests/test_modal_trial.py +61 -0
- wsm-0.1.0/tests/test_program.py +270 -0
- wsm-0.1.0/tests/test_spindle_owner.py +43 -0
- wsm-0.1.0/tests/test_trial.py +121 -0
- wsm-0.1.0/tests/test_trial_distillation.py +158 -0
- wsm-0.1.0/tests/test_trial_editor.py +117 -0
- wsm-0.1.0/tests/test_trial_poc.py +222 -0
- wsm-0.1.0/tests/test_trial_spindle.py +53 -0
- wsm-0.1.0/tests/test_trial_training.py +154 -0
wsm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wsm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: WSM (Weight Space Mods): Python model weight editing
|
|
5
|
+
Project-URL: Repository, https://github.com/thoriqakbar0/wsm
|
|
6
|
+
Project-URL: Documentation, https://github.com/thoriqakbar0/wsm/blob/main/README.md
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Provides-Extra: tinker
|
|
10
|
+
Requires-Dist: tinker==0.39.0; extra == "tinker"
|
|
11
|
+
Provides-Extra: trial
|
|
12
|
+
Requires-Dist: modal==1.6.3; extra == "trial"
|
|
13
|
+
Requires-Dist: tinker==0.39.0; extra == "trial"
|
|
14
|
+
Requires-Dist: jinja2>=3.1.6; extra == "trial"
|
|
15
|
+
Provides-Extra: spindle
|
|
16
|
+
Requires-Dist: modal==1.6.3; extra == "spindle"
|
|
17
|
+
Requires-Dist: tinker==0.39.0; extra == "spindle"
|
|
18
|
+
Requires-Dist: jinja2>=3.1.6; extra == "spindle"
|
|
19
|
+
Provides-Extra: editing
|
|
20
|
+
Requires-Dist: torch; extra == "editing"
|
|
21
|
+
Requires-Dist: transformers; extra == "editing"
|
|
22
|
+
Requires-Dist: safetensors; extra == "editing"
|
|
23
|
+
|
|
24
|
+
# WSM (Weight Space Mods)
|
|
25
|
+
|
|
26
|
+
WSM is a Python SDK for model weight editing. Access named tensors, change weights, evaluate the result, and save a standalone checkpoint.
|
|
27
|
+
|
|
28
|
+
Requires Python 3.12 or later. The core package has no third-party runtime dependencies.
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install wsm
|
|
32
|
+
wsm --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Install the model dependencies to load a local snapshot:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
pip install 'wsm[editing]'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from wsm import WeightModel
|
|
43
|
+
|
|
44
|
+
model = WeightModel.load("/path/to/local/snapshot")
|
|
45
|
+
model.weights["model.norm.weight"][:16].mul_(0.95)
|
|
46
|
+
result = model.evaluate([{"prompt": "Say hello.", "target": "Hello!"}])
|
|
47
|
+
model.save("/path/to/new-checkpoint")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The supported Python API consists of `WeightModel` and `execute_program`. Read the [project documentation](https://github.com/thoriqakbar0/wsm#readme) for CLI usage, execution trust, and optional Modal and Spindle trials. Spindle requires a separate pinned source installation documented in the [trial guide](https://github.com/thoriqakbar0/wsm/blob/main/docs/trials.md).
|
wsm-0.1.0/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# WSM (Weight Space Mods)
|
|
2
|
+
|
|
3
|
+
WSM is a Python SDK for model weight editing. Access named tensors, change weights, evaluate the result, and save a standalone checkpoint.
|
|
4
|
+
|
|
5
|
+
Requires Python 3.12 or later. The core package has no third-party runtime dependencies.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install wsm
|
|
9
|
+
wsm --help
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Install the model dependencies to load a local snapshot:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pip install 'wsm[editing]'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from wsm import WeightModel
|
|
20
|
+
|
|
21
|
+
model = WeightModel.load("/path/to/local/snapshot")
|
|
22
|
+
model.weights["model.norm.weight"][:16].mul_(0.95)
|
|
23
|
+
result = model.evaluate([{"prompt": "Say hello.", "target": "Hello!"}])
|
|
24
|
+
model.save("/path/to/new-checkpoint")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The supported Python API consists of `WeightModel` and `execute_program`. Read the [project documentation](https://github.com/thoriqakbar0/wsm#readme) for CLI usage, execution trust, and optional Modal and Spindle trials. Spindle requires a separate pinned source installation documented in the [trial guide](https://github.com/thoriqakbar0/wsm/blob/main/docs/trials.md).
|
wsm-0.1.0/pyproject.toml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wsm"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "WSM (Weight Space Mods): Python model weight editing"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Repository = "https://github.com/thoriqakbar0/wsm"
|
|
15
|
+
Documentation = "https://github.com/thoriqakbar0/wsm/blob/main/README.md"
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
wsm = "wsm.cli:main"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
tinker = ["tinker==0.39.0"]
|
|
22
|
+
trial = ["modal==1.6.3", "tinker==0.39.0", "jinja2>=3.1.6"]
|
|
23
|
+
spindle = ["modal==1.6.3", "tinker==0.39.0", "jinja2>=3.1.6"]
|
|
24
|
+
editing = ["torch", "transformers", "safetensors"]
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
wsm-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Python tools for editing, evaluating, and saving model weights."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
__all__ = ["WeightModel", "execute_program"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def __getattr__(name):
|
|
8
|
+
if name == "WeightModel":
|
|
9
|
+
from .editing import WeightModel
|
|
10
|
+
return WeightModel
|
|
11
|
+
if name == "execute_program":
|
|
12
|
+
from .program import execute_program
|
|
13
|
+
return execute_program
|
|
14
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Atomic checkpoint publication without replacing an existing destination."""
|
|
2
|
+
import ctypes
|
|
3
|
+
import errno
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from .errors import WeightlessError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _publish(source, destination):
|
|
10
|
+
rename = getattr(ctypes.CDLL(None, use_errno=True), "renameat2", None)
|
|
11
|
+
if rename is None:
|
|
12
|
+
raise WeightlessError("unsupported_platform", "Atomic exclusive export requires renameat2")
|
|
13
|
+
rename.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]
|
|
14
|
+
rename.restype = ctypes.c_int
|
|
15
|
+
if rename(-100, os.fsencode(source), -100, os.fsencode(destination), 1) != 0:
|
|
16
|
+
code = ctypes.get_errno()
|
|
17
|
+
if code in (errno.EEXIST, errno.ENOTEMPTY):
|
|
18
|
+
raise WeightlessError("export_exists", "Destination already exists")
|
|
19
|
+
raise OSError(code, os.strerror(code), str(destination))
|
wsm-0.1.0/src/wsm/cli.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Commands for trusted Python weight editing."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .errors import WeightlessError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parser():
|
|
12
|
+
root = argparse.ArgumentParser(prog="wsm", description="Edit model weights with trusted Python. No command opens the TUI.")
|
|
13
|
+
root.add_argument('--version', action='version', version=f'wsm {__version__}')
|
|
14
|
+
commands = root.add_subparsers(dest='command')
|
|
15
|
+
execute = commands.add_parser('execute', help='Execute a trusted Python file defining edit(model)')
|
|
16
|
+
execute.add_argument('program', type=Path)
|
|
17
|
+
execute.add_argument('--model', type=Path, required=True)
|
|
18
|
+
execute.add_argument('--output', type=Path, required=True)
|
|
19
|
+
execute.add_argument('--cases', type=Path, required=True)
|
|
20
|
+
execute.add_argument('--device', default='cpu')
|
|
21
|
+
commands.add_parser('tui', help='Open the Python editing terminal')
|
|
22
|
+
trial = commands.add_parser("trial", help="Run or watch an experimental Modal coding trial")
|
|
23
|
+
trial_commands = trial.add_subparsers(dest="trial_command", required=True)
|
|
24
|
+
preflight = trial_commands.add_parser("preflight", help="Validate explicit task, policy, and price configuration offline")
|
|
25
|
+
preflight.add_argument("--config", type=Path, required=True)
|
|
26
|
+
trial_run = trial_commands.add_parser("run", help="Run one fixed-model Modal trial and hosted Tinker update")
|
|
27
|
+
trial_run.add_argument("--config", type=Path, required=True)
|
|
28
|
+
trial_run.add_argument("--output", type=Path, required=True)
|
|
29
|
+
trial_view = trial_commands.add_parser("view", help="Read the public trial journal without control access")
|
|
30
|
+
trial_view.add_argument("directory", type=Path)
|
|
31
|
+
trial_view.add_argument("--follow", action="store_true")
|
|
32
|
+
weights = trial_commands.add_parser("weights", help="Read retained tensor shapes or numerical values")
|
|
33
|
+
weights.add_argument("checkpoint", type=Path, help="Retained safetensors file")
|
|
34
|
+
weights.add_argument("--tensor")
|
|
35
|
+
weights.add_argument("--indices", type=int, nargs="+")
|
|
36
|
+
return root
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main(argv=None):
|
|
40
|
+
args = parser().parse_args(argv)
|
|
41
|
+
try:
|
|
42
|
+
if args.command == "trial":
|
|
43
|
+
from .trial_poc import inspect_weights, load_config, run_poc, watch
|
|
44
|
+
if args.trial_command == "view":
|
|
45
|
+
watch(args.directory, follow=args.follow)
|
|
46
|
+
elif args.trial_command == "weights":
|
|
47
|
+
print(json.dumps(inspect_weights(args.checkpoint, tensor=args.tensor, indices=args.indices),
|
|
48
|
+
indent=2, allow_nan=False))
|
|
49
|
+
else:
|
|
50
|
+
config = load_config(args.config)
|
|
51
|
+
result = ({"valid": True, "cloud_calls": 0, "budget_usd": config["budget"]["max_usd"]}
|
|
52
|
+
if args.trial_command == "preflight" else run_poc(config, args.output))
|
|
53
|
+
print(json.dumps(result, indent=2, allow_nan=False))
|
|
54
|
+
return 0
|
|
55
|
+
if args.command in (None, 'tui'):
|
|
56
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
57
|
+
raise WeightlessError('terminal_required', 'The TUI requires a terminal; use --help for scriptable commands')
|
|
58
|
+
from .tui import launch
|
|
59
|
+
return launch()
|
|
60
|
+
from .program import execute_program
|
|
61
|
+
cases = json.loads(args.cases.read_text())
|
|
62
|
+
result = execute_program(args.program, model_path=args.model, output=args.output,
|
|
63
|
+
device=args.device, cases=cases)
|
|
64
|
+
print(json.dumps(result, allow_nan=False, indent=2))
|
|
65
|
+
return 0 if result.get('state') == 'published' else 1
|
|
66
|
+
except KeyboardInterrupt:
|
|
67
|
+
message = (f"Interrupted. Inspect {args.output / 'run.json'} and its checkpoint before retrying."
|
|
68
|
+
if args.command == 'execute' else 'Interrupted.')
|
|
69
|
+
print(json.dumps({'error': {'code': 'interrupted', 'message': message}}), file=sys.stderr)
|
|
70
|
+
return 130
|
|
71
|
+
except (Exception, SystemExit) as error:
|
|
72
|
+
print(json.dumps({'error': {'code': getattr(error, 'code', 'execution_error'),
|
|
73
|
+
'type': type(error).__name__, 'message': str(error)}}), file=sys.stderr)
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == '__main__':
|
|
78
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Terminal controls for the same Python execution command used by coding agents."""
|
|
2
|
+
import curses
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import signal
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def stop(process, first_signal):
|
|
13
|
+
try:
|
|
14
|
+
os.killpg(process.pid, first_signal)
|
|
15
|
+
except ProcessLookupError:
|
|
16
|
+
pass
|
|
17
|
+
try:
|
|
18
|
+
process.wait(timeout=10)
|
|
19
|
+
except subprocess.TimeoutExpired:
|
|
20
|
+
pass
|
|
21
|
+
finally:
|
|
22
|
+
try:
|
|
23
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
24
|
+
except ProcessLookupError:
|
|
25
|
+
pass
|
|
26
|
+
process.wait(timeout=10)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def execute(screen):
|
|
30
|
+
from .tui import prompt, put, show_result
|
|
31
|
+
|
|
32
|
+
program = Path(prompt(screen, "Python editing file defining edit(model)")).expanduser().resolve()
|
|
33
|
+
show_result(screen, "Review Python source before execution", program.read_text().splitlines())
|
|
34
|
+
model = Path(prompt(screen, "Local model snapshot directory")).expanduser().resolve()
|
|
35
|
+
cases = Path(prompt(screen, "Public evaluation cases JSON")).expanduser().resolve()
|
|
36
|
+
output = Path(prompt(screen, "New output directory")).expanduser().resolve()
|
|
37
|
+
device = prompt(screen, "Torch device", default="cpu")
|
|
38
|
+
command = [sys.executable, "-m", "wsm", "execute", str(program), "--model", str(model),
|
|
39
|
+
"--cases", str(cases), "--output", str(output), "--device", device]
|
|
40
|
+
process = None
|
|
41
|
+
with tempfile.TemporaryFile(mode="w+b") as log:
|
|
42
|
+
try:
|
|
43
|
+
process = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=log,
|
|
44
|
+
stderr=subprocess.STDOUT, start_new_session=True)
|
|
45
|
+
screen.timeout(100)
|
|
46
|
+
while process.poll() is None:
|
|
47
|
+
screen.erase()
|
|
48
|
+
put(screen, 1, "WSM / execute Python", attr=curses.A_BOLD)
|
|
49
|
+
put(screen, 3, str(program))
|
|
50
|
+
state = "starting"
|
|
51
|
+
try:
|
|
52
|
+
state = json.loads((output / "run.json").read_text()).get("state", state)
|
|
53
|
+
except (OSError, ValueError):
|
|
54
|
+
pass
|
|
55
|
+
put(screen, 5, "State " + state)
|
|
56
|
+
put(screen, 7, "Output " + str(output))
|
|
57
|
+
put(screen, 9, "Ctrl-C interrupts execution. Publication is recorded in run.json.")
|
|
58
|
+
screen.refresh()
|
|
59
|
+
try:
|
|
60
|
+
screen.get_wch()
|
|
61
|
+
except curses.error:
|
|
62
|
+
pass
|
|
63
|
+
except KeyboardInterrupt:
|
|
64
|
+
if process is not None:
|
|
65
|
+
stop(process, signal.SIGINT)
|
|
66
|
+
finally:
|
|
67
|
+
screen.timeout(-1)
|
|
68
|
+
if process is not None:
|
|
69
|
+
stop(process, signal.SIGTERM)
|
|
70
|
+
log.seek(0, 2)
|
|
71
|
+
log.seek(max(0, log.tell() - 16000))
|
|
72
|
+
text = log.read(16000).decode('utf-8', errors='replace')
|
|
73
|
+
try:
|
|
74
|
+
result = json.loads((output / "run.json").read_text())
|
|
75
|
+
except (OSError, ValueError):
|
|
76
|
+
result = {"state": "unknown", "output": str(output),
|
|
77
|
+
"detail": "Inspect output before retrying. No completion receipt was readable."}
|
|
78
|
+
show_result(screen, "Python execution result", {"run": result, "process_output": text})
|
|
79
|
+
return result
|