betterwithage commited on
Commit
4b82273
·
verified ·
1 Parent(s): 247eda4

Launch bounded immutable GGUF inference on free CPU

Browse files

Public cpu-basic Docker Space. Exact Q4_K_M artifact is preloaded at an immutable revision, rehashed before load, and served with strict input/output/concurrency limits plus health and identity endpoints. No secrets or paid hardware.

Files changed (8) hide show
  1. .dockerignore +6 -0
  2. Dockerfile +25 -0
  3. LICENSE +201 -0
  4. README.md +57 -6
  5. app.py +366 -0
  6. release.json +13 -0
  7. requirements.txt +6 -0
  8. tests/test_app.py +50 -0
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ .git
2
+ __pycache__
3
+ *.pyc
4
+ .pytest_cache
5
+ local-evidence
6
+ *.gguf
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim-bookworm@sha256:b18992999dbe963a45a8a4da40ac2b1975be1a776d939d098c647482bcad5cba
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1 \
4
+ PYTHONUNBUFFERED=1 \
5
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
6
+ HF_HUB_DISABLE_TELEMETRY=1 \
7
+ DO_NOT_TRACK=1 \
8
+ HF_HUB_OFFLINE=1 \
9
+ HOME=/home/user \
10
+ PATH=/home/user/.local/bin:${PATH}
11
+
12
+ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libgomp1 \
13
+ && rm -rf /var/lib/apt/lists/* \
14
+ && useradd --create-home --uid 1000 user
15
+ USER user
16
+ WORKDIR /home/user/app
17
+
18
+ COPY --chown=user:user requirements.txt ./requirements.txt
19
+ RUN python -m pip install --no-cache-dir --user -r requirements.txt
20
+ COPY --chown=user:user . .
21
+
22
+ EXPOSE 7860
23
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=120s --retries=3 \
24
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:7860/health', timeout=4)" || exit 1
25
+ CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1", "--limit-concurrency", "16", "--timeout-keep-alive", "5", "--no-server-header"]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 SZL HOLDINGS LLC
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,10 +1,61 @@
1
  ---
2
- title: Szl Model Inference Lab
3
- emoji: 🦀
4
- colorFrom: green
5
- colorTo: gray
6
  sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SZL Model Inference Lab
3
+ emoji: 🧪
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
8
+ license: apache-2.0
9
+ short_description: Bounded GGUF inference on free CPU, immutable identity.
10
+ models:
11
+ - SZLHOLDINGS/SZL-Khipu-1.5B-GGUF
12
+ tags:
13
+ - gguf
14
+ - llama.cpp
15
+ - cpu
16
+ - provenance
17
+ - bounded-inference
18
+ suggested_hardware: cpu-basic
19
+ startup_duration_timeout: 30m
20
+ preload_from_hub:
21
+ - SZLHOLDINGS/SZL-Khipu-1.5B-GGUF SZL-Khipu-1.5B-Q4_K_M.gguf,training_receipt.signed.json,eval_receipt.signed.json,owner_pubkey.json 67d60ec577730747055491640cfb91fc4a4b5d25
22
  ---
23
 
24
+ # SZL Model Inference Lab
25
+
26
+ A public, zero-secret, bounded CPU demonstration for the exact
27
+ `SZLHOLDINGS/SZL-Khipu-1.5B-Q4_K_M.gguf` bytes at immutable model commit
28
+ `67d60ec577730747055491640cfb91fc4a4b5d25`.
29
+
30
+ The runtime verifies the 986,047,904-byte file against SHA-256
31
+ `13c1a1993063e1dff92f7413ccf48eaca6d48efc8801ae9af35961ae3396623a`
32
+ before loading it. It requires no provider token or Space secret and is intended
33
+ for the Hub's free `cpu-basic` hardware only.
34
+
35
+ ## Boundaries
36
+
37
+ - One inference at a time; excess concurrent calls receive HTTP 429.
38
+ - 1,200 input characters, 32 generated tokens, and a 45-second generation budget.
39
+ - Greedy decoding (`temperature=0`); outputs are model-generated and may be wrong.
40
+ - `/health` distinguishes `STARTING`, `READY`, and `FAILED`.
41
+ - `/api/v1/identity` exposes the immutable artifact, runtime limits, source release
42
+ marker, and receipt boundary. Source checksums establish internal bundle
43
+ consistency only; they are not external authorship evidence.
44
+ - Prompts are not intentionally persisted by this source.
45
+
46
+ The upstream training and evaluation receipts are checked against the repository's
47
+ declared Ed25519 owner key and chained canonical payload hash. That is **owner-key
48
+ continuity**, not independent authorship evidence. Those receipts do not cover the
49
+ GGUF quantization, this Space's source, runtime outputs, independent benchmarking,
50
+ or safety certification.
51
+
52
+ ## Attribution and licenses
53
+
54
+ Space source: Apache-2.0, copyright SZL HOLDINGS LLC.
55
+
56
+ - Runtime model: [SZL-Khipu-1.5B-GGUF](https://huggingface.co/SZLHOLDINGS/SZL-Khipu-1.5B-GGUF), Apache-2.0.
57
+ - Fine-tuned model: [SZL-Khipu-1.5B-BrainNavigator](https://huggingface.co/SZLHOLDINGS/SZL-Khipu-1.5B-BrainNavigator), Apache-2.0.
58
+ - Base model: [Qwen2.5-1.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct), Apache-2.0.
59
+ - Inference binding: [llama-cpp-python v0.3.21](https://github.com/abetlen/llama-cpp-python/releases/tag/v0.3.21), MIT; CPU wheel URL and SHA-256 are pinned in `requirements.txt`.
60
+
61
+ No independent benchmark, post-quantization evaluation, or safety certification is claimed.
app.py ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import hashlib
5
+ import json
6
+ import os
7
+ import platform
8
+ import threading
9
+ import time
10
+ import urllib.request
11
+ from contextlib import asynccontextmanager
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from fastapi import FastAPI, HTTPException, Request
16
+ from fastapi.responses import HTMLResponse, JSONResponse
17
+ from huggingface_hub import hf_hub_download
18
+ from pydantic import BaseModel, Field, field_validator
19
+
20
+
21
+ SPACE_ID = "SZLHOLDINGS/szl-model-inference-lab"
22
+ MODEL_REPO = "SZLHOLDINGS/SZL-Khipu-1.5B-GGUF"
23
+ MODEL_REVISION = "67d60ec577730747055491640cfb91fc4a4b5d25"
24
+ MODEL_FILE = "SZL-Khipu-1.5B-Q4_K_M.gguf"
25
+ MODEL_SIZE = 986_047_904
26
+ MODEL_SHA256 = "13c1a1993063e1dff92f7413ccf48eaca6d48efc8801ae9af35961ae3396623a"
27
+ RECEIPT_FILES = (
28
+ "training_receipt.signed.json",
29
+ "eval_receipt.signed.json",
30
+ "owner_pubkey.json",
31
+ )
32
+ MAX_INPUT_CHARS = 1_200
33
+ MAX_NEW_TOKENS = 32
34
+ INFERENCE_BUDGET_SECONDS = 45.0
35
+ SOURCE_ROOT = Path(__file__).resolve().parent
36
+
37
+
38
+ state: dict[str, Any] = {
39
+ "status": "STARTING",
40
+ "failure_code": None,
41
+ "model_path": None,
42
+ "model_sha256": None,
43
+ "source_integrity": False,
44
+ "receipt_status": "NOT_CHECKED",
45
+ "llama_cpp_version": None,
46
+ }
47
+ llm: Any = None
48
+ inference_lock = threading.Lock()
49
+
50
+
51
+ def sha256_file(path: Path) -> str:
52
+ digest = hashlib.sha256()
53
+ with path.open("rb") as handle:
54
+ for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
55
+ digest.update(chunk)
56
+ return digest.hexdigest()
57
+
58
+
59
+ def load_release_manifest() -> dict[str, Any]:
60
+ manifest = json.loads((SOURCE_ROOT / "release.json").read_text(encoding="utf-8"))
61
+ for relative, expected in manifest["source_files"].items():
62
+ if sha256_file(SOURCE_ROOT / relative) != expected:
63
+ raise RuntimeError("SOURCE_INTEGRITY_MISMATCH")
64
+ return manifest
65
+
66
+
67
+ def artifact_path(filename: str) -> Path:
68
+ override = os.getenv("MODEL_DIR_OVERRIDE")
69
+ if override:
70
+ return Path(override) / filename
71
+ return Path(
72
+ hf_hub_download(
73
+ repo_id=MODEL_REPO,
74
+ filename=filename,
75
+ revision=MODEL_REVISION,
76
+ local_files_only=True,
77
+ token=False,
78
+ )
79
+ )
80
+
81
+
82
+ def verify_receipts() -> dict[str, Any]:
83
+ from cryptography.hazmat.primitives.serialization import load_der_public_key
84
+
85
+ owner = json.loads(artifact_path("owner_pubkey.json").read_text(encoding="utf-8"))
86
+ receipts: dict[str, dict[str, Any]] = {}
87
+ for filename in RECEIPT_FILES[:2]:
88
+ receipt = json.loads(artifact_path(filename).read_text(encoding="utf-8"))
89
+ canonical = json.dumps(
90
+ receipt["payload"], ensure_ascii=False, separators=(",", ":"), sort_keys=True
91
+ )
92
+ if canonical != receipt["canonical"]:
93
+ raise RuntimeError("RECEIPT_CANONICAL_MISMATCH")
94
+ if receipt["publicKeySpkiBase64"] != owner["publicKeySpkiBase64"]:
95
+ raise RuntimeError("RECEIPT_KEY_MISMATCH")
96
+ public_key = load_der_public_key(base64.b64decode(receipt["publicKeySpkiBase64"]))
97
+ public_key.verify(
98
+ base64.b64decode(receipt["signatureBase64"]), canonical.encode("utf-8")
99
+ )
100
+ receipts[filename] = receipt
101
+ training_digest = hashlib.sha256(
102
+ receipts["training_receipt.signed.json"]["canonical"].encode("utf-8")
103
+ ).hexdigest()
104
+ if (
105
+ receipts["eval_receipt.signed.json"]["payload"]["trainingReceiptSha256"]
106
+ != training_digest
107
+ ):
108
+ raise RuntimeError("RECEIPT_CHAIN_MISMATCH")
109
+ return {
110
+ "status": "OWNER_SIGNATURES_VALID",
111
+ "key_id": owner["keyId"],
112
+ "training_canonical_sha256": training_digest,
113
+ "eval_canonical_sha256": hashlib.sha256(
114
+ receipts["eval_receipt.signed.json"]["canonical"].encode("utf-8")
115
+ ).hexdigest(),
116
+ }
117
+
118
+
119
+ def initialize() -> None:
120
+ global llm
121
+ try:
122
+ manifest = load_release_manifest()
123
+ state["source_integrity"] = True
124
+ state["release_id"] = manifest["release_id"]
125
+
126
+ model_path = artifact_path(MODEL_FILE)
127
+ if model_path.stat().st_size != MODEL_SIZE:
128
+ raise RuntimeError("MODEL_SIZE_MISMATCH")
129
+ actual_sha = sha256_file(model_path)
130
+ if actual_sha != MODEL_SHA256:
131
+ raise RuntimeError("MODEL_SHA256_MISMATCH")
132
+ state["model_path"] = str(model_path)
133
+ state["model_sha256"] = actual_sha
134
+
135
+ receipt = verify_receipts()
136
+ state["receipt_status"] = receipt["status"]
137
+ state["receipt_evidence"] = receipt
138
+
139
+ import llama_cpp
140
+ from llama_cpp import Llama
141
+
142
+ state["llama_cpp_version"] = llama_cpp.__version__
143
+ threads = max(1, min(int(os.getenv("CPU_CORES", "2")), 2))
144
+ llm = Llama(
145
+ model_path=str(model_path),
146
+ n_ctx=1024,
147
+ n_batch=64,
148
+ n_threads=threads,
149
+ n_threads_batch=threads,
150
+ seed=0,
151
+ use_mmap=True,
152
+ use_mlock=False,
153
+ verbose=False,
154
+ )
155
+ state["status"] = "READY"
156
+ except Exception as exc: # keep /health available for honest diagnostics
157
+ state["status"] = "FAILED"
158
+ state["failure_code"] = str(exc) if str(exc).isupper() else type(exc).__name__
159
+
160
+
161
+ @asynccontextmanager
162
+ async def lifespan(_: FastAPI):
163
+ state["status"] = "STARTING"
164
+ initialize()
165
+ yield
166
+
167
+
168
+ app = FastAPI(
169
+ title="SZL Model Inference Lab",
170
+ version="1.0.0",
171
+ docs_url=None,
172
+ redoc_url=None,
173
+ openapi_url="/api/openapi.json",
174
+ lifespan=lifespan,
175
+ )
176
+
177
+
178
+ @app.middleware("http")
179
+ async def bounded_requests(request: Request, call_next):
180
+ if request.method == "POST":
181
+ raw_length = request.headers.get("content-length")
182
+ if raw_length:
183
+ try:
184
+ content_length = int(raw_length)
185
+ except ValueError:
186
+ return JSONResponse({"detail": "invalid content-length"}, status_code=400)
187
+ if content_length < 0:
188
+ return JSONResponse({"detail": "invalid content-length"}, status_code=400)
189
+ if content_length > 8192:
190
+ return JSONResponse({"detail": "request body too large"}, status_code=413)
191
+ response = await call_next(request)
192
+ response.headers["X-Content-Type-Options"] = "nosniff"
193
+ response.headers["Referrer-Policy"] = "no-referrer"
194
+ response.headers["X-Frame-Options"] = "SAMEORIGIN"
195
+ return response
196
+
197
+
198
+ class InferenceRequest(BaseModel):
199
+ prompt: str = Field(min_length=1, max_length=MAX_INPUT_CHARS)
200
+ max_new_tokens: int = Field(default=24, ge=1, le=MAX_NEW_TOKENS)
201
+
202
+ @field_validator("prompt")
203
+ @classmethod
204
+ def clean_prompt(cls, value: str) -> str:
205
+ value = value.strip()
206
+ if not value or "\x00" in value:
207
+ raise ValueError("prompt must contain visible text and no NUL bytes")
208
+ return value
209
+
210
+
211
+ class TimeBudgetStop:
212
+ def __init__(self, started: float, budget: float) -> None:
213
+ self.started = started
214
+ self.budget = budget
215
+
216
+ def __call__(self, _input_ids: Any, _logits: Any) -> bool:
217
+ return time.monotonic() - self.started >= self.budget
218
+
219
+
220
+ def identity_payload() -> dict[str, Any]:
221
+ manifest = json.loads((SOURCE_ROOT / "release.json").read_text(encoding="utf-8"))
222
+ return {
223
+ "schema": "szl.hf-free-inference-identity/v1",
224
+ "status": state["status"],
225
+ "space": {
226
+ "id": os.getenv("SPACE_ID", SPACE_ID),
227
+ "release_id": manifest["release_id"],
228
+ "source_integrity": state["source_integrity"],
229
+ "source_integrity_meaning": (
230
+ "internal release-file checksum consistency; not external authorship evidence"
231
+ ),
232
+ "license": "Apache-2.0",
233
+ },
234
+ "hardware": {
235
+ "required": "cpu-basic",
236
+ "accelerator_observed": os.getenv("ACCELERATOR", "none"),
237
+ "cpu_cores_observed": os.getenv("CPU_CORES", "unknown"),
238
+ "memory_observed": os.getenv("MEMORY", "unknown"),
239
+ },
240
+ "model": {
241
+ "repo": MODEL_REPO,
242
+ "revision": MODEL_REVISION,
243
+ "file": MODEL_FILE,
244
+ "size": MODEL_SIZE,
245
+ "sha256_expected": MODEL_SHA256,
246
+ "sha256_loaded": state["model_sha256"],
247
+ "base_model": "Qwen/Qwen2.5-1.5B-Instruct",
248
+ "license": "Apache-2.0",
249
+ },
250
+ "runtime": {
251
+ "python": platform.python_version(),
252
+ "llama_cpp_python": state["llama_cpp_version"],
253
+ "concurrency": 1,
254
+ "max_input_chars": MAX_INPUT_CHARS,
255
+ "max_new_tokens": MAX_NEW_TOKENS,
256
+ "inference_budget_seconds": INFERENCE_BUDGET_SECONDS,
257
+ },
258
+ "receipt_boundary": {
259
+ "status": state["receipt_status"],
260
+ "evidence": state.get("receipt_evidence"),
261
+ "covers": "owner-key continuity for upstream training/eval receipt payloads",
262
+ "does_not_cover": [
263
+ "Space source authorship",
264
+ "GGUF quantization quality",
265
+ "this runtime's outputs",
266
+ "independent benchmarking",
267
+ "safety certification",
268
+ ],
269
+ },
270
+ "failure_code": state["failure_code"],
271
+ }
272
+
273
+
274
+ @app.get("/health")
275
+ def health() -> JSONResponse:
276
+ code = 200 if state["status"] == "READY" else 503
277
+ return JSONResponse(
278
+ {
279
+ "status": state["status"],
280
+ "model_sha256_verified": state["model_sha256"] == MODEL_SHA256,
281
+ "source_integrity": state["source_integrity"],
282
+ "receipt_status": state["receipt_status"],
283
+ "failure_code": state["failure_code"],
284
+ },
285
+ status_code=code,
286
+ )
287
+
288
+
289
+ @app.get("/api/v1/identity")
290
+ def identity() -> dict[str, Any]:
291
+ return identity_payload()
292
+
293
+
294
+ @app.post("/api/v1/infer")
295
+ def infer(request: InferenceRequest) -> dict[str, Any]:
296
+ if state["status"] != "READY" or llm is None:
297
+ raise HTTPException(status_code=503, detail="runtime is not ready")
298
+ if not inference_lock.acquire(blocking=False):
299
+ raise HTTPException(status_code=429, detail="one inference is already running")
300
+ started = time.monotonic()
301
+ try:
302
+ stream = llm.create_chat_completion(
303
+ messages=[
304
+ {
305
+ "role": "system",
306
+ "content": (
307
+ "You are a bounded research demo. Answer briefly. If evidence is missing, "
308
+ "say so; do not claim independent benchmarking or safety certification."
309
+ ),
310
+ },
311
+ {"role": "user", "content": request.prompt},
312
+ ],
313
+ max_tokens=request.max_new_tokens,
314
+ temperature=0.0,
315
+ top_p=1.0,
316
+ top_k=1,
317
+ stop=["<|im_end|>", "<|endoftext|>"],
318
+ stream=True,
319
+ )
320
+ chunks: list[str] = []
321
+ finish_reason = None
322
+ timed_out = False
323
+ try:
324
+ for event in stream:
325
+ if time.monotonic() - started >= INFERENCE_BUDGET_SECONDS:
326
+ timed_out = True
327
+ break
328
+ choice = event["choices"][0]
329
+ chunks.append(choice.get("delta", {}).get("content") or "")
330
+ finish_reason = choice.get("finish_reason") or finish_reason
331
+ finally:
332
+ close = getattr(stream, "close", None)
333
+ if close:
334
+ close()
335
+ output = "".join(chunks).strip()
336
+ return {
337
+ "output": output,
338
+ "elapsed_ms": round((time.monotonic() - started) * 1000),
339
+ "finish_reason": "time_budget" if timed_out else finish_reason,
340
+ "model": {
341
+ "repo": MODEL_REPO,
342
+ "revision": MODEL_REVISION,
343
+ "file": MODEL_FILE,
344
+ "sha256": MODEL_SHA256,
345
+ },
346
+ "determinism": "greedy temperature=0; exact bytes and runtime are disclosed",
347
+ }
348
+ finally:
349
+ inference_lock.release()
350
+
351
+
352
+ @app.get("/", response_class=HTMLResponse)
353
+ def index() -> str:
354
+ return """<!doctype html>
355
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
356
+ <title>SZL Model Inference Lab</title><style>
357
+ body{margin:0;background:#080b13;color:#e9efff;font:16px/1.5 system-ui,sans-serif}main{max-width:820px;margin:auto;padding:40px 22px}
358
+ .card{background:#101728;border:1px solid #2a385b;border-radius:18px;padding:24px;box-shadow:0 18px 60px #0007}h1{margin:.1em 0;color:#8ce8ff}
359
+ textarea{box-sizing:border-box;width:100%;min-height:130px;background:#080d19;color:#fff;border:1px solid #40537f;border-radius:10px;padding:12px}
360
+ button{margin-top:12px;background:#7ce6ff;color:#06101a;border:0;border-radius:999px;padding:10px 18px;font-weight:700;cursor:pointer}pre{white-space:pre-wrap;background:#080d19;padding:14px;border-radius:10px;min-height:52px}code{color:#a7f3d0}.fine{color:#aebbd4;font-size:.9rem}a{color:#8ce8ff}</style></head>
361
+ <body><main><div class="card"><p class="fine">FREE CPU BASIC · ONE REQUEST AT A TIME · IMMUTABLE Q4_K_M</p><h1>SZL Model Inference Lab</h1>
362
+ <p>Real, bounded CPU inference for <code>SZL-Khipu-1.5B-GGUF</code>. Max 1,200 input characters and 32 generated tokens.</p>
363
+ <textarea id="prompt" maxlength="1200">Reply with one short sentence describing what a cryptographic receipt can prove.</textarea><br>
364
+ <button id="run">Run bounded inference</button><pre id="out">Ready.</pre>
365
+ <p class="fine">Owner-signed upstream receipts prove owner-key continuity only. No independent benchmark, post-quantization quality claim, or safety certification. Prompts are not intentionally stored. <a href="/api/v1/identity">Machine identity</a> · <a href="/health">Health</a></p></div></main>
366
+ <script>const b=document.querySelector('#run'),o=document.querySelector('#out'),p=document.querySelector('#prompt');b.onclick=async()=>{b.disabled=true;o.textContent='Running on free CPU…';try{const r=await fetch('/api/v1/infer',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({prompt:p.value,max_new_tokens:24})});const j=await r.json();o.textContent=r.ok?j.output:JSON.stringify(j)}catch(e){o.textContent='Request failed: '+e}finally{b.disabled=false}}</script></body></html>"""
release.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema": "szl.space-source-release/v1",
3
+ "release_id": "3b817b07-873a-4c11-ada3-72364fcf6731",
4
+ "source_files": {
5
+ ".dockerignore": "2f498f75a9cf48b332ab65dbdd1d387279c11eae9cdf7131e406f7cae61f709a",
6
+ "Dockerfile": "eb022050a39b0eafaa8916a883e94ca35f378308503262d6c3f1e239eda6444e",
7
+ "LICENSE": "0a41cda63c0751e5cdb240864525fc8bf29b2d3f2cb5c6406b9bae97f1e1407b",
8
+ "README.md": "9f46d43f4517fd44d85257a1abfcf5be5ba7387aa4a37dfdd1f7aeccc7685f4a",
9
+ "app.py": "3ed2bd3302e8dcd994ae4530bea827f9592b1d3af0b2ce69f76be8f490036004",
10
+ "requirements.txt": "74e8d7b61052bff09996927411d40603da8534e3953726303073fc6f6ec778f4",
11
+ "tests/test_app.py": "8a85490aee58bb6ad1368898d68ffc85d55279230c9c42cf9fdee7dbed716c81"
12
+ }
13
+ }
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.139.0
2
+ uvicorn==0.51.0
3
+ huggingface-hub==1.23.0
4
+ pydantic==2.13.4
5
+ cryptography==49.0.0
6
+ llama-cpp-python @ https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.21/llama_cpp_python-0.3.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=d9d4e0cc1a7f779dd224d0e07ac8ea793a58b345723ba90cd68ee20acf85ad68
tests/test_app.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+ from unittest.mock import patch
6
+
7
+ import app
8
+
9
+
10
+ class AppContractTests(unittest.TestCase):
11
+ def test_immutable_model_contract(self):
12
+ self.assertEqual(len(app.MODEL_REVISION), 40)
13
+ self.assertEqual(len(app.MODEL_SHA256), 64)
14
+ self.assertEqual(app.MODEL_SIZE, 986_047_904)
15
+ self.assertEqual(app.MAX_NEW_TOKENS, 32)
16
+
17
+ def test_prompt_contract(self):
18
+ self.assertEqual(app.InferenceRequest(prompt=" hello ").prompt, "hello")
19
+ with self.assertRaises(ValueError):
20
+ app.InferenceRequest(prompt=" ")
21
+ with self.assertRaises(ValueError):
22
+ app.InferenceRequest(prompt="x" * 1_201)
23
+
24
+ def test_time_budget_stop(self):
25
+ with patch("app.time.monotonic", return_value=10.0):
26
+ stop = app.TimeBudgetStop(0.0, 5.0)
27
+ self.assertTrue(stop(None, None))
28
+
29
+ def test_generation_budget_is_bounded(self):
30
+ self.assertLessEqual(app.INFERENCE_BUDGET_SECONDS, 45.0)
31
+ self.assertLessEqual(app.MAX_NEW_TOKENS, 32)
32
+
33
+ def test_sha256_file(self):
34
+ with tempfile.TemporaryDirectory() as directory:
35
+ path = Path(directory) / "sample"
36
+ path.write_bytes(b"abc")
37
+ self.assertEqual(
38
+ app.sha256_file(path),
39
+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
40
+ )
41
+
42
+ def test_identity_is_explicit_about_receipt_boundary(self):
43
+ payload = app.identity_payload()
44
+ boundary = json.dumps(payload["receipt_boundary"])
45
+ self.assertIn("independent benchmarking", boundary)
46
+ self.assertIn("safety certification", boundary)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ unittest.main()