deltaleague 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.
@@ -0,0 +1,25 @@
1
+ # Delta submission format version 1
2
+
3
+ A directory contains `manifest.json` and `weights.safetensors`. The immutable parent version and its canonical content hash are mandatory. All tensor payloads use safetensors; no pickle or participant code is loaded.
4
+
5
+ ```json
6
+ {
7
+ "schema_version": "delta.submission.v1",
8
+ "reconstruction_version": "delta-reconstruct-v1",
9
+ "model_config_id": "qwen35-08b-v1",
10
+ "parent_version_id": "UUID",
11
+ "parent_content_hash": "64 lowercase hex characters",
12
+ "format": "sparse-diff",
13
+ "operation": "replace",
14
+ "artifacts": [{"name": "weights.safetensors", "sha256": "64 lowercase hex characters", "size_bytes": 1024}],
15
+ "tensors": {"model.language_model.layers.0.mlp.down_proj.weight": {"indices": "t0.indices", "values": "t0.values"}}
16
+ }
17
+ ```
18
+
19
+ Sparse indices are int64, flat, strictly increasing and in bounds. Values are one-dimensional and match index count. Replacement sets those elements; addition accumulates parent and delta in float32 before casting to the registered parent dtype. Untouched tensors inherit the parent. `dense-diff` uses a `values` tensor matching the full target shape. `lora` maps each target to `a`, `b`, and finite `alpha`; apply `W + (alpha/r) * (B @ A)` in float32, then cast. A is `[r, in]`, B is `[out, r]`, rank is 1–256. `full` uses exact parent tensor names and shapes; changed frozen tensors are rejected.
20
+
21
+ Canonical identity is SHA-256 over canonical JSON identity metadata and sorted named tensors, each with a length-prefixed JSON header (name, shape, dtype) followed by contiguous raw CPU bytes. Model/config/tokenizer identity participates in the hash. Server reconstruction is authoritative. Different submissions may resolve to one model content hash without sharing provenance.
22
+
23
+ Client requests use `Authorization: Bearer <delta-token>`. Request `/api/v1/upload-sessions` with the manifest, PUT bytes to returned URLs, then POST `/api/v1/submissions` with `upload_id`, `title`, and an `Idempotency-Key` header. Retrying a submission uses the same key and upload. Signed URLs are credentials; do not publish them. Single-object uploads support the first model; multipart/resumable uploads remain a launch-completion task.
24
+
25
+ All metrics are JSON. Validation is the equally weighted mean of configured benchmark scores on a 0–100 scale. Private test is a single overall score. A missing score is null, never zero. Private example text and generations are not returned. Public validation can be downloaded through `/api/v1/evaluation-configs/<id>`.
@@ -0,0 +1,75 @@
1
+ # Run and evaluate locally
2
+
3
+ Local inference uses Transformers and PyTorch. Apple Silicon Macs select MPS, Linux machines with a compatible PyTorch GPU build select CUDA, and other machines use CPU. `--device cpu`, `--device mps`, or `--device cuda` overrides selection. There is no rented compute, server evaluation quota, or automatic submission involved.
4
+
5
+ ## Install
6
+
7
+ From the CLI repository, in a Python 3.12+ virtual environment:
8
+
9
+ ```sh
10
+ pip install ".[local]"
11
+ delta competition init qwen35-08b-v1 --output experiment
12
+ cd experiment
13
+ ```
14
+
15
+ The extra installs the tested Transformers version and math scorer. For Linux CUDA acceleration, install a compatible CUDA build of PyTorch using [the official installer](https://pytorch.org/get-started/locally/). If your existing PyTorch build is CPU-only, installing the CLI extra will not replace it with a CUDA build. For IFEval configurations, additionally install the CLI's `[ifeval]` extra.
16
+
17
+ The portable implementation loads full weights, without automatic quantization. Allow several GB of free RAM for Qwen3.5-0.8B and more for long contexts. CPU/MPS use float32; CUDA uses BF16 where supported. On Macs, unsupported MPS operations can fall back to CPU. If your model encounters an unsupported operator or memory error, try `--device cpu` and lower the output budget. macOS acceleration has not been verified on physical Apple hardware in this release; Linux CPU has been exercised with the actual Qwen checkpoint. CUDA selection is implemented but has not been hardware-tested for this CLI release.
18
+
19
+ ## Ask the model
20
+
21
+ ```sh
22
+ delta run "What is 2 + 2?" --max-new-tokens 64 --no-thinking
23
+ delta run "Explain your solution to 17 * 23." --max-new-tokens 512 --json
24
+ ```
25
+
26
+ `run` defaults to 256 output tokens. Thinking behavior otherwise comes from `evaluation.json`. A short budget may end before the final answer; the command reports that. Text goes to stdout, progress to stderr. `--json` adds provenance and token statistics. `--seed` defaults to 1000. Use `--workspace PATH` to work from another directory.
27
+
28
+ ## Run any leaderboard entry
29
+
30
+ ```sh
31
+ delta leaderboard --model qwen35-08b-v1
32
+ # Copy the desired entry's version_id, not its submission ID.
33
+ delta run "What is 2 + 2?" --version VERSION_ID --no-thinking
34
+ delta evaluate --version VERSION_ID --limit 2 --max-new-tokens 256 --output entry-quick.json
35
+ ```
36
+
37
+ `--version` downloads the immutable resulting checkpoint, verifies its hashes, and caches it under `.delta/models/VERSION_ID` inside the workspace. It works for the original model and submitted model versions, regardless of whether the submission used a LoRA, diff, or full weights. The version must belong to the workspace's model. Downloads need network access but not login. The inference runs locally; no private test data is downloaded. Completed cached files are reused on subsequent commands.
38
+
39
+ For a fully offline workspace based on that entry, run `delta competition init MODEL_ID --parent VERSION_ID --output another-experiment` while online, then run/evaluate from that workspace without `--version`.
40
+
41
+ ## Try your own edit or patch
42
+
43
+ ```sh
44
+ delta run "What is 2 + 2?" --weights edited.safetensors --no-thinking
45
+ delta evaluate --weights edited.safetensors --limit 2 --max-new-tokens 256 --output edit-quick.json
46
+
47
+ delta evaluate --patch submission --limit 2 --max-new-tokens 256 --output patch-quick.json
48
+ ```
49
+
50
+ `--weights` takes a full modified safetensors checkpoint, using the workspace parent's tokenizer and configuration. `--patch` takes a submission directory with `manifest.json`; it checks the parent and artifact hashes, then reconstructs supported LoRA/sparse/dense/full artifacts into a temporary checkpoint. Neither changes the parent. You can combine either with `--version` when editing that leaderboard checkpoint; a patch must reference that exact version. Temporary reconstructed files need disk space roughly equal to a full checkpoint and are removed on completion.
51
+
52
+ ## Evaluate public validation
53
+
54
+ Start small:
55
+
56
+ ```sh
57
+ delta evaluate --limit 2 --max-new-tokens 256 --output baseline-quick.json
58
+ ```
59
+
60
+ `--limit` selects the first N problems **per benchmark**; `--benchmark gsm8k` selects one benchmark. These are development checks, not representative estimates of the full public score.
61
+
62
+ For the entire public set with its configured generation settings:
63
+
64
+ ```sh
65
+ delta evaluate --output baseline-full.json
66
+ delta evaluate --weights edited.safetensors --output edit-full.json
67
+ ```
68
+
69
+ Full evaluation can be expensive locally: the starter configuration permits 32,768 generated tokens per example. The CLI prints the number of generations and upper token budget before loading the model. Use Ctrl-C to interrupt. Completed generation records remain in the JSONL file; no complete score report is written for an interrupted run. Choose a fresh output filename to rerun; automatic resume is not implemented.
70
+
71
+ The JSON report contains per-benchmark scores, the equal-weight mean across selected benchmarks, per-example sample averaging, checkpoint hash, source version, validation hash, runtime versions, actual generation settings, coverage, and truncation statistics. A companion `*.generations.jsonl` contains answers and per-sample scores. Existing output files are never overwritten.
72
+
73
+ The local scorer shares the platform's answer-scoring implementation: reasoning traces before `</think>` are excluded, math uses math-verify, and multiple-choice answers follow the same extraction rules. Configured samples use the same per-sample seeds. `--max-new-tokens` and `--thinking/--no-thinking` overrides are recorded explicitly.
74
+
75
+ Scores remain **advisory local results**. Transformers, vLLM, device precision, and sampling implementations can differ even with the same seed. Compare candidates using the same local settings, then submit for the official validation and private test scores. Local evaluation does not upload results or assign a leaderboard rank.
@@ -0,0 +1,5 @@
1
+ include README.md ARTIFACTS.md LOCAL.md
2
+ prune .github
3
+ prune .delta
4
+ prune var
5
+ global-exclude *.safetensors *.bin *.pt *.pth .env .env.* auth.json .delta-upload.json *.generations.jsonl
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: deltaleague
3
+ Version: 0.1.0
4
+ Summary: CLI for Delta League model-weight competitions, submissions, and local evaluation
5
+ Author: Delta League
6
+ Project-URL: Homepage, https://deltaleague.ai
7
+ Project-URL: Repository, https://github.com/ilblackdragon/deltaleague
8
+ Project-URL: Documentation, https://github.com/ilblackdragon/deltaleague/blob/main/README.md
9
+ Project-URL: Issues, https://github.com/ilblackdragon/deltaleague/issues
10
+ Keywords: llm,benchmark,model-weights,leaderboard
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: httpx<1,>=0.28
19
+ Requires-Dist: typer<1,>=0.16
20
+ Requires-Dist: safetensors<1,>=0.5
21
+ Requires-Dist: torch>=2.6
22
+ Provides-Extra: local
23
+ Requires-Dist: transformers==5.22.1; extra == "local"
24
+ Requires-Dist: math-verify==0.9.0; extra == "local"
25
+ Provides-Extra: ifeval
26
+ Requires-Dist: lm-eval<0.5,>=0.4.9; extra == "ifeval"
27
+
28
+ # Delta League CLI
29
+
30
+ The CLI and submission tools for [Delta League](https://deltaleague.ai): the open competition to improve intelligence.
31
+
32
+ Submit a patch. Prove an improvement. Advance the model.
33
+
34
+ Repository: [ilblackdragon/deltaleague](https://github.com/ilblackdragon/deltaleague). Private during launch preparation; intended to become public. The platform is maintained separately. This package contains no private test data.
35
+
36
+ ## Quick start
37
+
38
+ Requires Python 3.12+. Clone this repository, then install `pip install .` in a virtual environment. CPU PyTorch is sufficient for downloading and building patches; inference requires separate compute.
39
+
40
+ ```sh
41
+ delta competition list
42
+ delta competition init qwen35-08b-v1 --output experiment
43
+ cd experiment
44
+ ```
45
+
46
+ This selects a model competition and downloads its original checkpoint into `model/`, public examples into `validation.json`, and scoring/generation settings into `evaluation.json`. `delta-workspace.json` records the API, immutable parent version, and evaluation version. Use `--parent VERSION_ID` to start from another submission in the same competition. Downloads verify hashes, reuse completed files on retry, and avoid overwriting modified files.
47
+
48
+ Have your agent edit `model/model.safetensors` into a **separate** `edited.safetensors` file. Preserve tensor names, shapes, and dtypes. Keep the parent unchanged. Then:
49
+
50
+ ```sh
51
+ delta patch build --parent model --modified edited.safetensors --output submission
52
+ delta validate submission
53
+ delta login --no-browser
54
+ # Open the research PR first; replace PR_URL with its actual URL.
55
+ delta submit submission --title "My weight edit" --pr PR_URL
56
+ delta submission status SUBMISSION_ID --watch
57
+ delta submission results SUBMISSION_ID --json
58
+ delta leaderboard --model qwen35-08b-v1
59
+ ```
60
+
61
+ Before submitting, open a research PR in [the public repository](https://github.com/ilblackdragon/deltaleague) containing your hypothesis, research trace, edit/reproduction code, parent and evaluation IDs, seeds, and public validation findings. Attach its actual URL with `--pr`. The CLI records the link; it does not create the PR. Source-free submissions remain supported by omitting `--pr`.
62
+
63
+ Open the authorization URL in a browser and sign in with GitHub. `delta login` opens the browser automatically; `--no-browser` suits remote shells and agents. Authentication is only required for submission/account actions. Credentials are stored with mode 0600 and scoped to the API origin. Put `--api URL` before a command for another deployment.
64
+
65
+ ## Run and evaluate on your computer
66
+
67
+ Install with `pip install ".[local]"` from this repository, then run these inside an initialized competition workspace:
68
+
69
+ ```sh
70
+ delta run "What is 2 + 2?" --no-thinking
71
+ delta evaluate --limit 2 --max-new-tokens 256 --output baseline-quick.json
72
+ delta evaluate --weights edited.safetensors --limit 2 --max-new-tokens 256 --output edit-quick.json
73
+ # Any entry from `delta leaderboard` can be run or evaluated by version_id:
74
+ delta run "What is 2 + 2?" --version VERSION_ID --no-thinking
75
+ ```
76
+
77
+ Macs use MPS where available; Linux uses CUDA where available; CPU is supported on both. Local scores are advisory. See [LOCAL.md](LOCAL.md) for installation, full public evaluation, applying submission patches, hardware limits, and reproducibility details.
78
+
79
+ ## Agent starting prompt
80
+
81
+ > Work on Delta League using its CLI. Run `delta competition list`, select `qwen35-08b-v1`, and initialize a fresh experiment with `delta competition init`. Read evaluation.json and validation.json. Install the CLI local extra and use `delta run` for inference and `delta evaluate` for public validation; start with a bounded --limit and --max-new-tokens budget, recording the overrides. Use --version VERSION_ID to test a leaderboard entry, --weights for a full edited checkpoint, or --patch for a submission directory. Keep the downloaded parent unchanged and save candidate weights separately. Record your hypothesis, changes, and public validation results. Build and validate a patch. Before submitting, open a research PR in ilblackdragon/deltaleague with the hypothesis, research trace, reproducible code, parent/evaluation IDs, seeds, and public validation findings. Exclude credentials, private test data, and weight artifacts. Ask me to complete browser authorization if needed, then submit with `delta submit submission --title "SHORT DESCRIPTION" --pr ACTUAL_PR_URL` and watch the result. The CLI does not create the PR automatically. If I explicitly choose a source-free submission, omit `--pr`. Update the research PR with the submission link and verified results when available. Never request private test examples or credentials. The server's official validation and private test scores are authoritative; local validation scores are advisory.
82
+
83
+ See [the published-edit reproduction recipe](examples/blog-reproduction/README.md) for a concrete first experiment and [ARTIFACTS.md](ARTIFACTS.md) for LoRA, sparse/dense diff, and full-checkpoint formats. `delta validate` checks manifest/file integrity; the server also checks parent compatibility, numerical validity, and reconstruction. JSON output supports agent use. Public validation scores and private test scores share one leaderboard row, ranked by private test score.
@@ -0,0 +1,56 @@
1
+ # Delta League CLI
2
+
3
+ The CLI and submission tools for [Delta League](https://deltaleague.ai): the open competition to improve intelligence.
4
+
5
+ Submit a patch. Prove an improvement. Advance the model.
6
+
7
+ Repository: [ilblackdragon/deltaleague](https://github.com/ilblackdragon/deltaleague). Private during launch preparation; intended to become public. The platform is maintained separately. This package contains no private test data.
8
+
9
+ ## Quick start
10
+
11
+ Requires Python 3.12+. Clone this repository, then install `pip install .` in a virtual environment. CPU PyTorch is sufficient for downloading and building patches; inference requires separate compute.
12
+
13
+ ```sh
14
+ delta competition list
15
+ delta competition init qwen35-08b-v1 --output experiment
16
+ cd experiment
17
+ ```
18
+
19
+ This selects a model competition and downloads its original checkpoint into `model/`, public examples into `validation.json`, and scoring/generation settings into `evaluation.json`. `delta-workspace.json` records the API, immutable parent version, and evaluation version. Use `--parent VERSION_ID` to start from another submission in the same competition. Downloads verify hashes, reuse completed files on retry, and avoid overwriting modified files.
20
+
21
+ Have your agent edit `model/model.safetensors` into a **separate** `edited.safetensors` file. Preserve tensor names, shapes, and dtypes. Keep the parent unchanged. Then:
22
+
23
+ ```sh
24
+ delta patch build --parent model --modified edited.safetensors --output submission
25
+ delta validate submission
26
+ delta login --no-browser
27
+ # Open the research PR first; replace PR_URL with its actual URL.
28
+ delta submit submission --title "My weight edit" --pr PR_URL
29
+ delta submission status SUBMISSION_ID --watch
30
+ delta submission results SUBMISSION_ID --json
31
+ delta leaderboard --model qwen35-08b-v1
32
+ ```
33
+
34
+ Before submitting, open a research PR in [the public repository](https://github.com/ilblackdragon/deltaleague) containing your hypothesis, research trace, edit/reproduction code, parent and evaluation IDs, seeds, and public validation findings. Attach its actual URL with `--pr`. The CLI records the link; it does not create the PR. Source-free submissions remain supported by omitting `--pr`.
35
+
36
+ Open the authorization URL in a browser and sign in with GitHub. `delta login` opens the browser automatically; `--no-browser` suits remote shells and agents. Authentication is only required for submission/account actions. Credentials are stored with mode 0600 and scoped to the API origin. Put `--api URL` before a command for another deployment.
37
+
38
+ ## Run and evaluate on your computer
39
+
40
+ Install with `pip install ".[local]"` from this repository, then run these inside an initialized competition workspace:
41
+
42
+ ```sh
43
+ delta run "What is 2 + 2?" --no-thinking
44
+ delta evaluate --limit 2 --max-new-tokens 256 --output baseline-quick.json
45
+ delta evaluate --weights edited.safetensors --limit 2 --max-new-tokens 256 --output edit-quick.json
46
+ # Any entry from `delta leaderboard` can be run or evaluated by version_id:
47
+ delta run "What is 2 + 2?" --version VERSION_ID --no-thinking
48
+ ```
49
+
50
+ Macs use MPS where available; Linux uses CUDA where available; CPU is supported on both. Local scores are advisory. See [LOCAL.md](LOCAL.md) for installation, full public evaluation, applying submission patches, hardware limits, and reproducibility details.
51
+
52
+ ## Agent starting prompt
53
+
54
+ > Work on Delta League using its CLI. Run `delta competition list`, select `qwen35-08b-v1`, and initialize a fresh experiment with `delta competition init`. Read evaluation.json and validation.json. Install the CLI local extra and use `delta run` for inference and `delta evaluate` for public validation; start with a bounded --limit and --max-new-tokens budget, recording the overrides. Use --version VERSION_ID to test a leaderboard entry, --weights for a full edited checkpoint, or --patch for a submission directory. Keep the downloaded parent unchanged and save candidate weights separately. Record your hypothesis, changes, and public validation results. Build and validate a patch. Before submitting, open a research PR in ilblackdragon/deltaleague with the hypothesis, research trace, reproducible code, parent/evaluation IDs, seeds, and public validation findings. Exclude credentials, private test data, and weight artifacts. Ask me to complete browser authorization if needed, then submit with `delta submit submission --title "SHORT DESCRIPTION" --pr ACTUAL_PR_URL` and watch the result. The CLI does not create the PR automatically. If I explicitly choose a source-free submission, omit `--pr`. Update the research PR with the submission link and verified results when available. Never request private test examples or credentials. The server's official validation and private test scores are authoritative; local validation scores are advisory.
55
+
56
+ See [the published-edit reproduction recipe](examples/blog-reproduction/README.md) for a concrete first experiment and [ARTIFACTS.md](ARTIFACTS.md) for LoRA, sparse/dense diff, and full-checkpoint formats. `delta validate` checks manifest/file integrity; the server also checks parent compatibility, numerical validity, and reconstruction. JSON output supports agent use. Public validation scores and private test scores share one leaderboard row, ranked by private test score.
File without changes
@@ -0,0 +1,163 @@
1
+ """Portable weight artifact contract. This module never imports the platform."""
2
+ import hashlib
3
+ import json
4
+ import math
5
+ import re
6
+ import uuid
7
+ from pathlib import Path
8
+
9
+
10
+ def canonical_json(value):
11
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
12
+
13
+
14
+ def file_hash(path):
15
+ with open(path, "rb") as f:
16
+ return hashlib.file_digest(f, "sha256").hexdigest()
17
+
18
+
19
+ def validate_manifest(manifest):
20
+ if not isinstance(manifest, dict):
21
+ raise ValueError("invalid_manifest")
22
+ if manifest.get("schema_version") != "delta.submission.v1":
23
+ raise ValueError("unsupported_schema")
24
+ if manifest.get("format") not in {"sparse-diff", "dense-diff", "lora", "full"}:
25
+ raise ValueError("unsupported_format")
26
+ if manifest.get("reconstruction_version") != "delta-reconstruct-v1":
27
+ raise ValueError("unsupported_reconstruction")
28
+ try:
29
+ uuid.UUID(str(manifest["parent_version_id"]))
30
+ except (ValueError, KeyError):
31
+ raise ValueError("invalid_parent_version")
32
+ if not re.fullmatch(r"[0-9a-f]{64}", str(manifest.get("parent_content_hash", ""))):
33
+ raise ValueError("invalid_parent_hash")
34
+ if not re.fullmatch(r"[-a-zA-Z0-9_]{1,100}", str(manifest.get("model_config_id", ""))):
35
+ raise ValueError("invalid_model_config")
36
+ files = manifest.get("artifacts", [])
37
+ if not isinstance(files, list) or not all(isinstance(f, dict) for f in files):
38
+ raise ValueError("invalid_artifact_inventory")
39
+ if len(files) != 1 or files[0].get("name") != "weights.safetensors":
40
+ raise ValueError("expected_weights_safetensors")
41
+ for f in files:
42
+ if not re.fullmatch(r"[0-9a-f]{64}", f.get("sha256", "")):
43
+ raise ValueError("invalid_hash")
44
+ if type(f.get("size_bytes")) is not int or f["size_bytes"] <= 0:
45
+ raise ValueError("invalid_size")
46
+ if manifest.get("operation", "replace") not in {"replace", "add"}:
47
+ raise ValueError("unsupported_operation")
48
+ mapping = manifest.get("tensors", {})
49
+ if not isinstance(mapping, dict) or not all(isinstance(k, str) and isinstance(v, dict) for k, v in mapping.items()):
50
+ raise ValueError("invalid_tensor_mapping")
51
+ for target, spec in mapping.items():
52
+ if len(target) > 1024:
53
+ raise ValueError("invalid_tensor_name")
54
+ required = {"a", "b", "alpha"} if manifest["format"] == "lora" else ({"indices", "values"} if manifest["format"] == "sparse-diff" else {"values"})
55
+ if set(spec) != required or any(not isinstance(v, str) for k, v in spec.items() if k != "alpha"):
56
+ raise ValueError("invalid_tensor_mapping")
57
+ if len(mapping) > 10000:
58
+ raise ValueError("too_many_tensors")
59
+
60
+
61
+ def content_hash(tensors, identity_hash):
62
+ import torch
63
+ h = hashlib.sha256(canonical_json({"schema": "delta-content-v1", "identity": identity_hash}))
64
+ for name, tensor in sorted(tensors.items()):
65
+ t = tensor.detach().cpu().contiguous()
66
+ header = canonical_json([name, list(t.shape), str(t.dtype)])
67
+ h.update(len(header).to_bytes(8, "little"))
68
+ h.update(header)
69
+ h.update(t.reshape(-1).view(torch.uint8).numpy().tobytes())
70
+ return h.hexdigest()
71
+
72
+
73
+ def reconstruct(parent_path, artifact_path, manifest, allowed_prefix=""):
74
+ import torch
75
+ from safetensors.torch import load_file
76
+ validate_manifest(manifest)
77
+ base = load_file(str(parent_path))
78
+ patch = load_file(str(artifact_path))
79
+ if any(not torch.isfinite(t).all() for t in patch.values()):
80
+ raise ValueError("nonfinite_patch")
81
+ result = {k: v.clone() for k, v in base.items()}
82
+ used = set()
83
+ if manifest["format"] == "full":
84
+ if set(patch) != set(base):
85
+ raise ValueError("tensor_inventory")
86
+ mapping = {k: {"values": k} for k in patch}
87
+ else:
88
+ mapping = manifest.get("tensors", {})
89
+ if not mapping:
90
+ raise ValueError("empty_patch")
91
+ for name, spec in mapping.items():
92
+ if name not in base:
93
+ raise ValueError("unknown_tensor")
94
+ original = base[name]
95
+ if not name.startswith(allowed_prefix):
96
+ # Full checkpoints may carry unchanged frozen tensors.
97
+ if manifest["format"] == "full" and torch.equal(patch[name], original):
98
+ used.add(name)
99
+ continue
100
+ raise ValueError("frozen_tensor")
101
+ kind = manifest["format"]
102
+ if kind == "lora":
103
+ a, b = patch[spec["a"]], patch[spec["b"]]
104
+ used.update([spec["a"], spec["b"]])
105
+ if a.ndim != 2 or b.ndim != 2 or original.ndim != 2 or a.shape[0] != b.shape[1] or not 1 <= a.shape[0] <= 256:
106
+ raise ValueError("invalid_lora_rank")
107
+ alpha = float(spec["alpha"])
108
+ if not math.isfinite(alpha) or (b.shape[0], a.shape[1]) != tuple(original.shape):
109
+ raise ValueError("invalid_lora_shape")
110
+ updated = original.float() + (b.float() @ a.float()) * (alpha / a.shape[0])
111
+ else:
112
+ values = patch[spec["values"]]
113
+ used.add(spec["values"])
114
+ if kind == "sparse-diff":
115
+ indices = patch[spec["indices"]]
116
+ used.add(spec["indices"])
117
+ if indices.dtype != torch.int64 or indices.ndim != 1 or values.ndim != 1 or indices.numel() != values.numel():
118
+ raise ValueError("invalid_sparse_shape")
119
+ if indices.numel() and (indices[0] < 0 or indices[-1] >= original.numel() or not (indices[1:] > indices[:-1]).all()):
120
+ raise ValueError("invalid_sparse_indices")
121
+ updated = original.float().clone().reshape(-1)
122
+ updated[indices] = values.float() if manifest.get("operation", "replace") == "replace" else updated[indices] + values.float()
123
+ updated = updated.reshape(original.shape)
124
+ else:
125
+ if values.shape != original.shape:
126
+ raise ValueError("tensor_shape")
127
+ updated = values if kind == "full" or manifest.get("operation", "replace") == "replace" else original.float() + values.float()
128
+ result[name] = updated.to(original.dtype).contiguous()
129
+ if not torch.isfinite(result[name]).all():
130
+ raise ValueError("nonfinite_result")
131
+ if used != set(patch):
132
+ raise ValueError("unlisted_tensors")
133
+ return result
134
+
135
+
136
+ def build_sparse(parent, modified, output, parent_id, parent_hash, model_id):
137
+ import torch
138
+ from safetensors.torch import load_file, save_file
139
+ output = Path(output)
140
+ output.mkdir(parents=True, exist_ok=True)
141
+ before, after = load_file(str(parent)), load_file(str(modified))
142
+ if set(before) != set(after):
143
+ raise ValueError("tensor_inventory")
144
+ tensors, mapping = {}, {}
145
+ for number, name in enumerate(sorted(before)):
146
+ a, b = before[name], after[name]
147
+ if a.shape != b.shape or a.dtype != b.dtype:
148
+ raise ValueError("tensor_shape_or_dtype")
149
+ idx = torch.nonzero(a.reshape(-1) != b.reshape(-1)).flatten()
150
+ if idx.numel():
151
+ ik, vk = f"t{number}.indices", f"t{number}.values"
152
+ tensors[ik], tensors[vk] = idx, b.reshape(-1)[idx]
153
+ mapping[name] = {"indices": ik, "values": vk}
154
+ if not tensors:
155
+ raise ValueError("empty_patch")
156
+ path = output / "weights.safetensors"
157
+ save_file(tensors, str(path))
158
+ manifest = {"schema_version": "delta.submission.v1", "format": "sparse-diff", "operation": "replace",
159
+ "reconstruction_version": "delta-reconstruct-v1", "parent_version_id": str(parent_id),
160
+ "parent_content_hash": parent_hash, "model_config_id": model_id, "tensors": mapping,
161
+ "artifacts": [{"name": path.name, "sha256": file_hash(path), "size_bytes": path.stat().st_size}]}
162
+ (output / "manifest.json").write_text(json.dumps(manifest, indent=2))
163
+ return manifest
@@ -0,0 +1,193 @@
1
+ """Offline text inference and public validation; no platform or private-data imports."""
2
+ from contextlib import contextmanager
3
+ from importlib.metadata import version
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import statistics
8
+ import tempfile
9
+ import time
10
+
11
+ from .artifacts import file_hash, reconstruct, validate_manifest
12
+ from .scoring import score_answer
13
+
14
+
15
+ def read_workspace(workspace):
16
+ workspace = Path(workspace).resolve()
17
+ identity = json.loads((workspace / 'delta-workspace.json').read_text())
18
+ config = json.loads((workspace / 'evaluation.json').read_text())
19
+ metadata = json.loads((workspace / 'model/delta-model.json').read_text())
20
+ if (identity['config_id'] != config['id'] or identity['model_id'] != config['model_id']
21
+ or identity['parent_version_id'] != metadata['id'] or identity['model_id'] != metadata['model_id']):
22
+ raise ValueError('Workspace model, parent, or evaluation configuration mismatch. Initialize a fresh workspace.')
23
+ return workspace, identity, config, metadata
24
+
25
+
26
+ @contextmanager
27
+ def model_directory(workspace, metadata, weights=None, patch=None, parent=None):
28
+ """Overlay a candidate without mutating the downloaded parent or tokenizer."""
29
+ if weights and patch:
30
+ raise ValueError('Choose either --weights or --patch.')
31
+ parent = parent or workspace / 'model'
32
+ for artifact in metadata['files']:
33
+ name = artifact['name']
34
+ if Path(name).name != name:
35
+ raise ValueError('Invalid model artifact filename.')
36
+ if name == 'model.safetensors' and weights:
37
+ continue
38
+ path = parent / name
39
+ if path.stat().st_size != artifact['size_bytes'] or file_hash(path) != artifact['sha256']:
40
+ raise ValueError(f'Parent artifact changed: {name}. Keep the parent untouched and use --weights or --patch.')
41
+ if not weights and not patch:
42
+ yield parent, {'kind': 'parent', 'weights_sha256': file_hash(parent / 'model.safetensors')}
43
+ return
44
+ with tempfile.TemporaryDirectory(prefix='delta-local-') as temp:
45
+ target = Path(temp)
46
+ for artifact in metadata['files']:
47
+ if artifact['name'] != 'model.safetensors':
48
+ (target / artifact['name']).symlink_to((parent / artifact['name']).resolve())
49
+ if patch:
50
+ from safetensors.torch import save_file
51
+ patch = Path(patch).resolve()
52
+ manifest = json.loads((patch / 'manifest.json').read_text())
53
+ validate_manifest(manifest)
54
+ if (manifest['parent_version_id'] != metadata['id'] or manifest['parent_content_hash'] != metadata['content_hash']
55
+ or manifest['model_config_id'] != metadata['model_id']):
56
+ raise ValueError('Patch does not reference this workspace parent.')
57
+ for artifact in manifest['artifacts']:
58
+ path = patch / artifact['name']
59
+ if path.stat().st_size != artifact['size_bytes'] or file_hash(path) != artifact['sha256']:
60
+ raise ValueError('Patch artifact integrity failure.')
61
+ tensors = reconstruct(parent / 'model.safetensors', patch / manifest['artifacts'][0]['name'], manifest)
62
+ save_file(tensors, str(target / 'model.safetensors'))
63
+ del tensors
64
+ else:
65
+ weights = Path(weights).resolve()
66
+ if not weights.is_file() or weights.suffix != '.safetensors':
67
+ raise ValueError('--weights must be a full .safetensors checkpoint.')
68
+ (target / 'model.safetensors').symlink_to(weights)
69
+ yield target, {'kind': 'patch' if patch else 'weights', 'weights_sha256': file_hash(target / 'model.safetensors')}
70
+
71
+
72
+ def select_device(torch, requested):
73
+ if requested not in {'auto', 'cpu', 'cuda', 'mps'}:
74
+ raise ValueError('--device must be auto, cpu, cuda, or mps.')
75
+ if requested == 'auto':
76
+ return 'cuda' if torch.cuda.is_available() else ('mps' if torch.backends.mps.is_available() else 'cpu')
77
+ if requested == 'cuda' and not torch.cuda.is_available():
78
+ raise ValueError('CUDA is unavailable. Install a CUDA-enabled PyTorch build or use --device cpu.')
79
+ if requested == 'mps' and not torch.backends.mps.is_available():
80
+ raise ValueError('MPS is unavailable. Use a supported Apple Silicon Mac or --device cpu.')
81
+ return requested
82
+
83
+
84
+ class Runner:
85
+ def __init__(self, model_path, device='auto'):
86
+ import torch
87
+ from transformers import AutoConfig, AutoTokenizer, AutoModelForCausalLM, AutoModelForImageTextToText
88
+ self.torch = torch
89
+ self.device = select_device(torch, device)
90
+ # Float32 is the portable CPU/MPS path; CUDA uses BF16 when supported.
91
+ dtype = torch.bfloat16 if self.device == 'cuda' and torch.cuda.is_bf16_supported() else torch.float32
92
+ self.tokenizer = AutoTokenizer.from_pretrained(model_path, local_files_only=True, trust_remote_code=False)
93
+ config = AutoConfig.from_pretrained(model_path, local_files_only=True, trust_remote_code=False)
94
+ auto = AutoModelForImageTextToText if type(config) in AutoModelForImageTextToText._model_mapping else AutoModelForCausalLM
95
+ self.model, info = auto.from_pretrained(model_path, config=config, local_files_only=True, trust_remote_code=False,
96
+ use_safetensors=True, dtype=dtype, output_loading_info=True)
97
+ if info.get('missing_keys') or info.get('unexpected_keys') or info.get('mismatched_keys') or info.get('error_msgs'):
98
+ raise ValueError('Checkpoint tensors do not match the model configuration; refusing partially initialized weights.')
99
+ self.model.to(self.device).eval()
100
+ self.details = {'backend': 'transformers', 'transformers': version('transformers'), 'torch': torch.__version__,
101
+ 'device': self.device, 'dtype': str(dtype),
102
+ 'mps_cpu_fallback': self.device == 'mps' and os.getenv('PYTORCH_ENABLE_MPS_FALLBACK') == '1'}
103
+
104
+ def generate(self, prompt, generation, seed):
105
+ from transformers import LogitsProcessor, LogitsProcessorList, set_seed
106
+ torch = self.torch
107
+ rendered = self.tokenizer.apply_chat_template([{'role': 'user', 'content': prompt}], tokenize=False,
108
+ add_generation_prompt=True, enable_thinking=generation.get('enable_thinking', True))
109
+ inputs = self.tokenizer(rendered, return_tensors='pt', add_special_tokens=False).to(self.device)
110
+ prompt_length = inputs['input_ids'].shape[1]
111
+ max_tokens = generation.get('max_tokens', 32768)
112
+ if prompt_length + max_tokens > generation.get('max_model_len', 34816):
113
+ raise ValueError('Prompt plus output budget exceeds max_model_len; reduce --max-new-tokens.')
114
+ penalty = generation.get('presence_penalty', 1.5)
115
+
116
+ class PresencePenalty(LogitsProcessor):
117
+ def __call__(self, input_ids, scores):
118
+ # Match vLLM: presence penalty applies to generated tokens, not the prompt.
119
+ for row in range(input_ids.shape[0]):
120
+ ids = input_ids[row, prompt_length:].unique()
121
+ scores[row, ids] -= penalty
122
+ return scores
123
+
124
+ temperature = generation.get('temperature', .6)
125
+ kwargs = {'max_new_tokens': max_tokens, 'do_sample': temperature > 0,
126
+ 'logits_processor': LogitsProcessorList([PresencePenalty()])}
127
+ if temperature > 0:
128
+ kwargs.update(temperature=temperature, top_p=generation.get('top_p', .95), top_k=generation.get('top_k', 20))
129
+ set_seed(seed)
130
+ start = time.monotonic()
131
+ with torch.inference_mode():
132
+ tokens = self.model.generate(**inputs, **kwargs)[0, prompt_length:]
133
+ count = tokens.numel()
134
+ # Preserve </think> so the same scorer can remove reasoning traces as on the server.
135
+ text = self.tokenizer.decode(tokens, skip_special_tokens=False)
136
+ for token in [self.tokenizer.eos_token, self.tokenizer.pad_token]:
137
+ if token:
138
+ text = text.replace(token, '')
139
+ return {'text': text, 'tokens': count, 'truncated': count == max_tokens,
140
+ 'generation_seconds': time.monotonic() - start}
141
+
142
+
143
+ def public_examples(workspace, config, benchmark=None, limit=None):
144
+ path = workspace / 'validation.json'
145
+ if file_hash(path) != config['validation_sha256']:
146
+ raise ValueError('Public validation data changed; initialize a fresh workspace.')
147
+ data = json.loads(path.read_text())
148
+ names = config['validation_names']
149
+ if set(data) != set(names) or not names:
150
+ raise ValueError('Public validation benchmark inventory mismatch.')
151
+ if benchmark and benchmark not in names:
152
+ raise ValueError('Unknown benchmark; choose from: ' + ', '.join(names))
153
+ selected = [benchmark] if benchmark else names
154
+ rows = []
155
+ for name in selected:
156
+ if not data[name]:
157
+ raise ValueError(f'Empty benchmark: {name}')
158
+ for index, example in enumerate(data[name][:limit] if limit else data[name]):
159
+ scorer = example.get('scorer', 'exact')
160
+ if scorer not in {'exact', 'choice', 'math', 'ifeval'}:
161
+ raise ValueError(f'Unsupported scorer: {scorer}')
162
+ # Fail on missing scoring dependencies before spending time on inference.
163
+ if scorer == 'math':
164
+ __import__('math_verify')
165
+ elif scorer == 'ifeval':
166
+ __import__('lm_eval.tasks.ifeval.instructions_registry')
167
+ samples = example.get('samples', 1)
168
+ if type(samples) is not int or not 1 <= samples <= 16:
169
+ raise ValueError('Invalid sample count.')
170
+ for sample in range(samples):
171
+ rows.append({'benchmark': name, 'example_index': index, 'sample': sample,
172
+ 'seed': 1000 + sample, 'example': example})
173
+ return rows, selected, not benchmark and all(limit is None or limit >= len(v) for v in data.values())
174
+
175
+
176
+ def evaluate(runner, tasks, generation, stream, progress):
177
+ scores, tokens, seconds, truncated = {}, 0, 0, 0
178
+ for index, task in enumerate(tasks, 1):
179
+ result = runner.generate(task['example']['prompt'], generation, task['seed'])
180
+ score = score_answer(task['example'], result['text'])
181
+ scores.setdefault(task['benchmark'], {}).setdefault(task['example_index'], []).append(score)
182
+ row = {k: v for k, v in task.items() if k != 'example'}
183
+ row.update(example_id=task['example']['id'], score=score, **result)
184
+ stream.write(json.dumps(row) + '\n')
185
+ stream.flush()
186
+ tokens += result['tokens']
187
+ seconds += result['generation_seconds']
188
+ truncated += result['truncated']
189
+ progress(index, len(tasks), task['benchmark'])
190
+ components = {name: statistics.mean(statistics.mean(samples) for samples in examples.values()) * 100
191
+ for name, examples in scores.items()}
192
+ return {'validation_score': statistics.mean(components.values()), 'validation_benchmarks': components,
193
+ 'output_tokens': tokens, 'generation_seconds': seconds, 'truncation_rate': truncated / len(tasks)}