programasweights 0.4.5__tar.gz → 0.4.6__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.
- {programasweights-0.4.5 → programasweights-0.4.6}/AGENTS.md +6 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/CHANGELOG.md +8 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/PKG-INFO +13 -1
- {programasweights-0.4.5 → programasweights-0.4.6}/PYPI_README.md +12 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/README.md +12 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/api-reference/python-sdk.md +4 -1
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/api-reference/rest-api.md +10 -1
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/__init__.py +1 -1
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/runtime_llamacpp.py +38 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/pyproject.toml +1 -1
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_base_interpreter.py +204 -2
- {programasweights-0.4.5 → programasweights-0.4.6}/.github/workflows/test.yml +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/.gitignore +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/.readthedocs.yaml +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/LICENSE +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/adr/001-llama-cpp-over-pytorch.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/adr/002-q4_0-adapter-format.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/adr/003-single-spec-field.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/adr/004-compiler-naming.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/adr/005-vllm-hidden-states.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/adr/006-email-api-key-auth.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/advanced/adrs.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/advanced/architecture.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/api-reference/cli.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/architecture.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/case-studies/alien-taboo.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/case-studies/log-monitoring.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/case-studies/semantic-search.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/case-studies/site-navigation.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/case-studies/tool-calling.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/getting-started/first-program.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/getting-started/installation.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/getting-started/naming-programs.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/guide/browser-inference.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/guide/how-it-works.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/guide/local-inference.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/guide/writing-good-specs.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/hub/browsing-programs.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/hub/feedback-cases.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/hub/publishing-programs.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/index.md +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/docs/requirements.txt +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/examples/flask_app.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/examples/jupyter_notebook.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/examples/langchain_integration.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/examples/paw_monitor.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/examples/replace_openai.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/mkdocs.yml +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/_output.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/_program_reference.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/artifacts.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/cache.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/cli.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/client.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/compiler/__init__.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/compiler/dummy.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/config.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/convert_peft_to_paw.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/errors.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/local_program.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/paw_format.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/runtime/__init__.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/runtime/interpreter.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/programasweights/runtime/interpreter_onnx.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_api_errors.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_cli_auth.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_compile_timeouts.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_desktop_sdk.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_local_program.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_offline_cache.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_runtime_registry_sdk.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_sdk.py +0 -0
- {programasweights-0.4.5 → programasweights-0.4.6}/tests/test_sdk.sh +0 -0
|
@@ -43,6 +43,8 @@ fn = paw.compile_and_load("Classify sentiment as positive or negative")
|
|
|
43
43
|
fn("I love this!") # "positive"
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
Load a local `.paw` file with `paw.function("./classifier.paw")` (SDK 0.4.5+).
|
|
47
|
+
|
|
46
48
|
If you want the smaller browser-compatible runtime explicitly, pass `compiler="paw-4b-gpt2"`. Otherwise, omit `compiler` and let the server default decide.
|
|
47
49
|
|
|
48
50
|
## Current Public Compilers
|
|
@@ -89,6 +91,8 @@ Output: delete
|
|
|
89
91
|
- Spec + input + output share a ~2048 token context window. Inputs that exceed it will error.
|
|
90
92
|
- `max_tokens` defaults to `None`: generation runs until EOS or the context limit.
|
|
91
93
|
- Compile runs on the hosted PAW API. Inference should usually run locally through the SDK.
|
|
94
|
+
- Synchronous compile requests use a 40-minute read timeout.
|
|
95
|
+
- **Run local inference sequentially by default.** With PAW’s current llama.cpp backend, simultaneous inference calls often perform worse. Reuse loaded functions and process inputs one at a time; never call the same function instance concurrently.
|
|
92
96
|
- **GPU acceleration** is enabled by default (`n_gpu_layers=-1`). Uses Metal on Mac, CUDA on Linux, and falls back to CPU automatically. If GPU causes issues, set `PAW_GPU_LAYERS=0` or pass `n_gpu_layers=0`.
|
|
93
97
|
- **First call** is usually ~1-5s because it loads the base model. Subsequent calls are typically ~0.05-0.5s depending on input length and GPU availability.
|
|
94
98
|
- **Base model files are shared** across programs on disk. Each Standard LoRA adapter is ~22 MB; each Compact LoRA adapter is ~5 MB.
|
|
@@ -102,6 +106,8 @@ Output: delete
|
|
|
102
106
|
|
|
103
107
|
## Common Errors
|
|
104
108
|
|
|
109
|
+
Compile API HTTP errors raise `paw.APIError`. Check `error.code` and `error.message` for details. The SDK does not retry automatically.
|
|
110
|
+
|
|
105
111
|
| Error | Cause | Fix |
|
|
106
112
|
|-------|-------|-----|
|
|
107
113
|
| `RuntimeError: assets not ready` on download | Program is still generating after compile | The SDK polls automatically for up to 60s. If it still fails, retry shortly or recompile. |
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.6 (2026-09-13)
|
|
4
|
+
|
|
5
|
+
- Add an optional `logits_processor` argument to a compiled or base program
|
|
6
|
+
call for caller-supplied token constraints in llama.cpp's sampler.
|
|
7
|
+
Defaults to `None`; sampling is unchanged when it is unset.
|
|
8
|
+
- Propagate processor failures to the caller instead of letting native
|
|
9
|
+
callback errors silently continue with unconstrained output.
|
|
10
|
+
|
|
3
11
|
## 0.4.5 (2026-09-10)
|
|
4
12
|
|
|
5
13
|
- Expose structured compile API failures as `paw.APIError`, compatible with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: programasweights
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Compile natural language specifications into neural programs that run locally via llama.cpp.
|
|
5
5
|
Project-URL: Homepage, https://programasweights.com
|
|
6
6
|
Project-URL: Repository, https://github.com/programasweights/programasweights-python
|
|
@@ -84,6 +84,18 @@ If you need to inspect available compiler aliases programmatically, use `paw.lis
|
|
|
84
84
|
|
|
85
85
|
GPU acceleration is enabled by default (Metal on Mac, CUDA on Linux, falls back to CPU). Set `PAW_GPU_LAYERS=0` to force CPU if GPU causes issues.
|
|
86
86
|
|
|
87
|
+
## Constrained Decoding
|
|
88
|
+
|
|
89
|
+
In SDK 0.4.6+, a call accepts an optional `logits_processor`: an advanced hook for caller-supplied llama.cpp-compatible token constraints, not built-in regex or JSON-schema validation. It runs at every generation step. The default, `None`, keeps sampling unchanged.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import llama_cpp
|
|
93
|
+
# my_processor is your compatible callable: (input_ids, scores) -> scores.
|
|
94
|
+
fn("Office line: +1-555-666-7777", logits_processor=llama_cpp.LogitsProcessorList([my_processor]))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Processors see the full prompt and generated-token history, not just the output. Create or reset stateful processors for each call. Token limits and output whitespace trimming still apply, so validate the returned result.
|
|
98
|
+
|
|
87
99
|
## Desktop and Offline Workflows
|
|
88
100
|
|
|
89
101
|
Prepare and inspect validated local assets without keeping a model loaded:
|
|
@@ -53,6 +53,18 @@ If you need to inspect available compiler aliases programmatically, use `paw.lis
|
|
|
53
53
|
|
|
54
54
|
GPU acceleration is enabled by default (Metal on Mac, CUDA on Linux, falls back to CPU). Set `PAW_GPU_LAYERS=0` to force CPU if GPU causes issues.
|
|
55
55
|
|
|
56
|
+
## Constrained Decoding
|
|
57
|
+
|
|
58
|
+
In SDK 0.4.6+, a call accepts an optional `logits_processor`: an advanced hook for caller-supplied llama.cpp-compatible token constraints, not built-in regex or JSON-schema validation. It runs at every generation step. The default, `None`, keeps sampling unchanged.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import llama_cpp
|
|
62
|
+
# my_processor is your compatible callable: (input_ids, scores) -> scores.
|
|
63
|
+
fn("Office line: +1-555-666-7777", logits_processor=llama_cpp.LogitsProcessorList([my_processor]))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Processors see the full prompt and generated-token history, not just the output. Create or reset stateful processors for each call. Token limits and output whitespace trimming still apply, so validate the returned result.
|
|
67
|
+
|
|
56
68
|
## Desktop and Offline Workflows
|
|
57
69
|
|
|
58
70
|
Prepare and inspect validated local assets without keeping a model loaded:
|
|
@@ -53,6 +53,18 @@ If you need to inspect available compiler aliases programmatically, use `paw.lis
|
|
|
53
53
|
|
|
54
54
|
GPU acceleration is enabled by default (Metal on Mac, CUDA on Linux, falls back to CPU). Set `PAW_GPU_LAYERS=0` to force CPU if GPU causes issues.
|
|
55
55
|
|
|
56
|
+
## Constrained Decoding
|
|
57
|
+
|
|
58
|
+
In SDK 0.4.6+, a call accepts an optional `logits_processor`: an advanced hook for caller-supplied llama.cpp-compatible token constraints, not built-in regex or JSON-schema validation. It runs at every generation step. The default, `None`, keeps sampling unchanged.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import llama_cpp
|
|
62
|
+
# my_processor is your compatible callable: (input_ids, scores) -> scores.
|
|
63
|
+
fn("Office line: +1-555-666-7777", logits_processor=llama_cpp.LogitsProcessorList([my_processor]))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Processors see the full prompt and generated-token history, not just the output. Create or reset stateful processors for each call. Token limits and output whitespace trimming still apply, so validate the returned result.
|
|
67
|
+
|
|
56
68
|
## Desktop and Offline Workflows
|
|
57
69
|
|
|
58
70
|
Prepare and inspect validated local assets without keeping a model loaded:
|
|
@@ -44,7 +44,7 @@ bundle directly. Required runtime metadata and base models are cached for reuse.
|
|
|
44
44
|
The returned callable:
|
|
45
45
|
|
|
46
46
|
```python
|
|
47
|
-
output: str = fn(input_text, max_tokens=None, temperature=0.0)
|
|
47
|
+
output: str = fn(input_text, max_tokens=None, temperature=0.0, logits_processor=None)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
| Parameter | Description |
|
|
@@ -52,6 +52,9 @@ output: str = fn(input_text, max_tokens=None, temperature=0.0)
|
|
|
52
52
|
| `input_text` | Input string for the program. |
|
|
53
53
|
| `max_tokens` | Maximum tokens to generate. `None` (default) = use all remaining context window. |
|
|
54
54
|
| `temperature` | Sampling temperature (default `0.0`). |
|
|
55
|
+
| `logits_processor` | SDK 0.4.6+. Optional `llama_cpp.LogitsProcessorList` of caller-supplied processors, applied at every generation step. `None` (default) keeps sampling unchanged. |
|
|
56
|
+
|
|
57
|
+
This advanced hook is not built-in regex or JSON-schema validation. Each processor takes `(input_ids, scores)` and returns modified scores. Its token history includes the full prompt (including any compiled prefix and suffix or base-model template) plus generated tokens. Create or reset stateful processors for each call; processor exceptions propagate to the caller. Token limits and the usual output whitespace trimming still apply, so validate the returned result.
|
|
55
58
|
|
|
56
59
|
**Context limits:** Spec + input + output share a ~2048 token window. Inputs that exceed it will error. `max_tokens` defaults to `None`: generation runs until EOS or the context limit.
|
|
57
60
|
|
|
@@ -136,7 +136,16 @@ List available compiler models and identifiers for use with compile requests.
|
|
|
136
136
|
|
|
137
137
|
### `GET /health`
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
Returns HTTP 200 for API liveness. The JSON `status` is `healthy` only when
|
|
140
|
+
all enabled public compilers pass their provider checks; otherwise it is
|
|
141
|
+
`degraded`, with details in `warnings` and `gpu_services`, keyed by compiler
|
|
142
|
+
name. Finetune checks include its base compiler, durable Redis, and at least
|
|
143
|
+
one healthy worker. A healthy worker remains available while busy.
|
|
144
|
+
|
|
145
|
+
Checks run concurrently with a three-second timeout and share a five-second
|
|
146
|
+
cache. `queue_depth` counts waiting finetune jobs across distinct dispatchers,
|
|
147
|
+
not running jobs; it is `null` when a queue cannot be verified. This is a
|
|
148
|
+
readiness observation, not a guarantee that a new compilation will succeed.
|
|
140
149
|
|
|
141
150
|
## Errors
|
|
142
151
|
|
|
@@ -27,7 +27,7 @@ try:
|
|
|
27
27
|
from importlib.metadata import version as _meta_version
|
|
28
28
|
__version__ = _meta_version("programasweights")
|
|
29
29
|
except Exception:
|
|
30
|
-
__version__ = "0.4.
|
|
30
|
+
__version__ = "0.4.6"
|
|
31
31
|
|
|
32
32
|
from ._output import ProgressCallback, ProgressEvent, report_progress
|
|
33
33
|
from .cache import CachedProgram
|
|
@@ -397,6 +397,7 @@ class PawFunction:
|
|
|
397
397
|
input_text: str,
|
|
398
398
|
max_tokens: int | None = None,
|
|
399
399
|
temperature: float = 0.0,
|
|
400
|
+
logits_processor: llama_cpp.LogitsProcessorList | None = None,
|
|
400
401
|
) -> str:
|
|
401
402
|
"""Run the program on an input.
|
|
402
403
|
|
|
@@ -404,6 +405,11 @@ class PawFunction:
|
|
|
404
405
|
input_text: The input to process.
|
|
405
406
|
max_tokens: Maximum output tokens. None = use all remaining context.
|
|
406
407
|
temperature: Sampling temperature (0 = greedy).
|
|
408
|
+
logits_processor: Optional llama.cpp logits processors for
|
|
409
|
+
caller-supplied token constraints, applied at each generation
|
|
410
|
+
step with the full prompt and generated-token history.
|
|
411
|
+
Processor errors propagate to the caller. None = unchanged
|
|
412
|
+
sampling; callers must validate any structured output.
|
|
407
413
|
|
|
408
414
|
Returns:
|
|
409
415
|
The program's output as a string.
|
|
@@ -444,6 +450,7 @@ class PawFunction:
|
|
|
444
450
|
prompt_tokens,
|
|
445
451
|
max_tokens=max_tokens,
|
|
446
452
|
temperature=temperature,
|
|
453
|
+
logits_processor=logits_processor,
|
|
447
454
|
token_description="prompt",
|
|
448
455
|
)
|
|
449
456
|
|
|
@@ -459,6 +466,7 @@ class PawFunction:
|
|
|
459
466
|
input_tokens,
|
|
460
467
|
max_tokens=max_tokens,
|
|
461
468
|
temperature=temperature,
|
|
469
|
+
logits_processor=logits_processor,
|
|
462
470
|
prior_tokens=self._n_prefix,
|
|
463
471
|
token_description="input",
|
|
464
472
|
)
|
|
@@ -469,6 +477,7 @@ class PawFunction:
|
|
|
469
477
|
*,
|
|
470
478
|
max_tokens: int | None,
|
|
471
479
|
temperature: float,
|
|
480
|
+
logits_processor: llama_cpp.LogitsProcessorList | None = None,
|
|
472
481
|
prior_tokens: int = 0,
|
|
473
482
|
token_description: str,
|
|
474
483
|
) -> str:
|
|
@@ -487,11 +496,40 @@ class PawFunction:
|
|
|
487
496
|
if prompt_tokens:
|
|
488
497
|
self._llm.eval(prompt_tokens)
|
|
489
498
|
|
|
499
|
+
# Leave sample() unchanged when no processors were supplied. Native
|
|
500
|
+
# llama.cpp invokes processors through ctypes, which swallows callback
|
|
501
|
+
# exceptions, so defer those errors until control returns to Python.
|
|
502
|
+
sample_kwargs = {}
|
|
503
|
+
processor_error: BaseException | None = None
|
|
504
|
+
if logits_processor is not None:
|
|
505
|
+
def guarded_processor(input_ids, scores):
|
|
506
|
+
nonlocal processor_error
|
|
507
|
+
score_count = len(scores)
|
|
508
|
+
try:
|
|
509
|
+
for processor in logits_processor:
|
|
510
|
+
# Match native in-place conversion, including errors
|
|
511
|
+
# for invalid processor return shapes or values.
|
|
512
|
+
scores[:] = processor(input_ids, scores)
|
|
513
|
+
return scores
|
|
514
|
+
except BaseException as exc:
|
|
515
|
+
processor_error = exc
|
|
516
|
+
# The processor may have corrupted or made scores
|
|
517
|
+
# read-only. Return fresh, finite logits so native sampling
|
|
518
|
+
# can finish; its token will be discarded below.
|
|
519
|
+
return [0.0] * score_count
|
|
520
|
+
|
|
521
|
+
sample_kwargs["logits_processor"] = llama_cpp.LogitsProcessorList(
|
|
522
|
+
[guarded_processor]
|
|
523
|
+
)
|
|
524
|
+
|
|
490
525
|
output_tokens = []
|
|
491
526
|
for _ in range(gen_limit):
|
|
492
527
|
token = self._llm.sample(
|
|
493
528
|
temp=temperature if temperature > 0 else 0,
|
|
529
|
+
**sample_kwargs,
|
|
494
530
|
)
|
|
531
|
+
if processor_error is not None:
|
|
532
|
+
raise processor_error
|
|
495
533
|
|
|
496
534
|
if token == self._llm.token_eos():
|
|
497
535
|
break
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "programasweights"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.6"
|
|
8
8
|
description = "Compile natural language specifications into neural programs that run locally via llama.cpp."
|
|
9
9
|
readme = "PYPI_README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -4,6 +4,7 @@ import ctypes
|
|
|
4
4
|
import hashlib
|
|
5
5
|
import importlib
|
|
6
6
|
import json
|
|
7
|
+
import math
|
|
7
8
|
import sys
|
|
8
9
|
import threading
|
|
9
10
|
import types
|
|
@@ -74,6 +75,7 @@ def fake_runtime(monkeypatch):
|
|
|
74
75
|
self.input_ids = [0] * 4096
|
|
75
76
|
self.tokenize_calls = []
|
|
76
77
|
self.eval_calls = []
|
|
78
|
+
self.sample_calls = []
|
|
77
79
|
self.reset_calls = 0
|
|
78
80
|
self.closed = False
|
|
79
81
|
self._sample_index = 0
|
|
@@ -94,12 +96,25 @@ def fake_runtime(monkeypatch):
|
|
|
94
96
|
def eval(self, tokens):
|
|
95
97
|
copied = list(tokens)
|
|
96
98
|
self.eval_calls.append(copied)
|
|
99
|
+
self.input_ids[self.n_tokens:self.n_tokens + len(copied)] = copied
|
|
97
100
|
self.n_tokens += len(copied)
|
|
98
101
|
|
|
99
|
-
def sample(self, *, temp):
|
|
102
|
+
def sample(self, *, temp, logits_processor=None):
|
|
103
|
+
self.sample_calls.append(
|
|
104
|
+
{"temp": temp, "logits_processor": logits_processor}
|
|
105
|
+
)
|
|
100
106
|
token = ord("A") if self._sample_index == 0 else 0
|
|
101
107
|
self._sample_index += 1
|
|
102
|
-
|
|
108
|
+
if logits_processor is None:
|
|
109
|
+
return token
|
|
110
|
+
# Mirror llama.cpp: every processor sees the full token
|
|
111
|
+
# sequence so far and returns the scores to sample from.
|
|
112
|
+
scores = [0.0] * 256
|
|
113
|
+
scores[token] = 1.0
|
|
114
|
+
input_ids = list(self.input_ids[:self.n_tokens])
|
|
115
|
+
for processor in logits_processor:
|
|
116
|
+
scores = processor(input_ids, scores)
|
|
117
|
+
return max(range(len(scores)), key=scores.__getitem__)
|
|
103
118
|
|
|
104
119
|
def token_eos(self):
|
|
105
120
|
return 0
|
|
@@ -111,6 +126,7 @@ def fake_runtime(monkeypatch):
|
|
|
111
126
|
self.reset_calls += 1
|
|
112
127
|
self.n_tokens = 0
|
|
113
128
|
self._sample_index = 0
|
|
129
|
+
self.sample_calls = []
|
|
114
130
|
|
|
115
131
|
def close(self):
|
|
116
132
|
self.closed = True
|
|
@@ -144,7 +160,11 @@ def fake_runtime(monkeypatch):
|
|
|
144
160
|
decoded_path.write_bytes(state.prefix_save_bytes)
|
|
145
161
|
return state.prefix_save_result
|
|
146
162
|
|
|
163
|
+
class FakeLogitsProcessorList(list):
|
|
164
|
+
pass
|
|
165
|
+
|
|
147
166
|
module.Llama = FakeLlama
|
|
167
|
+
module.LogitsProcessorList = FakeLogitsProcessorList
|
|
148
168
|
module.llama_adapter_lora_init = adapter_init
|
|
149
169
|
module.llama_set_adapter_lora = adapter_apply
|
|
150
170
|
module.llama_adapter_lora_free = adapter_free
|
|
@@ -641,3 +661,185 @@ def test_native_stderr_redirection_is_serialized(
|
|
|
641
661
|
|
|
642
662
|
assert max_active == 1
|
|
643
663
|
assert operations == ["open", "open"]
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def _force_token(token: int):
|
|
667
|
+
"""A llama.cpp logits processor that always selects one token."""
|
|
668
|
+
seen = []
|
|
669
|
+
|
|
670
|
+
def processor(input_ids, scores):
|
|
671
|
+
seen.append(list(input_ids))
|
|
672
|
+
forced = list(scores)
|
|
673
|
+
forced[token] = max(scores) + 1.0
|
|
674
|
+
return forced
|
|
675
|
+
|
|
676
|
+
processor.seen = seen
|
|
677
|
+
return processor
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def test_generation_passes_no_logits_processor_by_default(
|
|
681
|
+
fake_runtime,
|
|
682
|
+
tmp_path,
|
|
683
|
+
monkeypatch,
|
|
684
|
+
):
|
|
685
|
+
runtime, state = fake_runtime
|
|
686
|
+
program_dir = _write_compiled_program(tmp_path)
|
|
687
|
+
|
|
688
|
+
fn = runtime.PawFunction(program_dir, offline=True)
|
|
689
|
+
instance = state.instances[-1]
|
|
690
|
+
temperatures = []
|
|
691
|
+
|
|
692
|
+
def sample_without_processor_keyword(*, temp):
|
|
693
|
+
temperatures.append(temp)
|
|
694
|
+
return ord("A")
|
|
695
|
+
|
|
696
|
+
monkeypatch.setattr(instance, "sample", sample_without_processor_keyword)
|
|
697
|
+
|
|
698
|
+
assert fn("payload", max_tokens=1) == "A"
|
|
699
|
+
assert temperatures == [0]
|
|
700
|
+
fn.close()
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def test_compiled_logits_processor_runs_on_every_generated_token(
|
|
704
|
+
fake_runtime,
|
|
705
|
+
tmp_path,
|
|
706
|
+
):
|
|
707
|
+
import llama_cpp
|
|
708
|
+
|
|
709
|
+
runtime, state = fake_runtime
|
|
710
|
+
program_dir = _write_compiled_program(tmp_path)
|
|
711
|
+
|
|
712
|
+
fn = runtime.PawFunction(program_dir, offline=True)
|
|
713
|
+
instance = state.instances[-1]
|
|
714
|
+
processor = _force_token(ord("B"))
|
|
715
|
+
processors = llama_cpp.LogitsProcessorList([processor])
|
|
716
|
+
|
|
717
|
+
output = fn("payload", max_tokens=3, logits_processor=processors)
|
|
718
|
+
|
|
719
|
+
assert output == "BBB"
|
|
720
|
+
assert len(instance.sample_calls) == 3
|
|
721
|
+
assert processor.seen == [
|
|
722
|
+
list(b"Prefix:payload:Suffix"),
|
|
723
|
+
list(b"Prefix:payload:SuffixB"),
|
|
724
|
+
list(b"Prefix:payload:SuffixBB"),
|
|
725
|
+
]
|
|
726
|
+
fn.close()
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
@pytest.mark.parametrize("interpreter", ["gpt2", "Qwen/Qwen3-0.6B"])
|
|
730
|
+
def test_base_logits_processor_is_passed_through(fake_runtime, interpreter):
|
|
731
|
+
import llama_cpp
|
|
732
|
+
|
|
733
|
+
_, state = fake_runtime
|
|
734
|
+
_write_base_model(interpreter)
|
|
735
|
+
|
|
736
|
+
fn = paw.function(None, interpreter=interpreter)
|
|
737
|
+
instance = state.instances[-1]
|
|
738
|
+
processor = _force_token(ord("C"))
|
|
739
|
+
processors = llama_cpp.LogitsProcessorList([processor])
|
|
740
|
+
|
|
741
|
+
assert fn("hello", max_tokens=2, logits_processor=processors) == "CC"
|
|
742
|
+
assert len(instance.sample_calls) == 2
|
|
743
|
+
prompt = list(instance.tokenize_calls[0]["data"])
|
|
744
|
+
assert processor.seen == [prompt, prompt + [ord("C")]]
|
|
745
|
+
fn.close()
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def test_logits_processor_can_select_eos(fake_runtime, tmp_path):
|
|
749
|
+
import llama_cpp
|
|
750
|
+
|
|
751
|
+
runtime, state = fake_runtime
|
|
752
|
+
fn = runtime.PawFunction(_write_compiled_program(tmp_path), offline=True)
|
|
753
|
+
instance = state.instances[-1]
|
|
754
|
+
processor = _force_token(instance.token_eos())
|
|
755
|
+
eval_count = len(instance.eval_calls)
|
|
756
|
+
|
|
757
|
+
assert fn(
|
|
758
|
+
"payload",
|
|
759
|
+
max_tokens=3,
|
|
760
|
+
logits_processor=llama_cpp.LogitsProcessorList([processor]),
|
|
761
|
+
) == ""
|
|
762
|
+
assert processor.seen == [list(b"Prefix:payload:Suffix")]
|
|
763
|
+
assert instance.eval_calls[eval_count:] == [list(b"payload:Suffix")]
|
|
764
|
+
fn.close()
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
@pytest.mark.parametrize("mode", ["compiled", "base"])
|
|
768
|
+
@pytest.mark.parametrize(
|
|
769
|
+
"exception_type,invalid_result",
|
|
770
|
+
[(ValueError, False), (KeyboardInterrupt, False), (SystemExit, False),
|
|
771
|
+
(TypeError, True)],
|
|
772
|
+
)
|
|
773
|
+
def test_logits_processor_errors_cross_native_callback_safely(
|
|
774
|
+
fake_runtime, tmp_path, monkeypatch, mode, exception_type, invalid_result,
|
|
775
|
+
):
|
|
776
|
+
import llama_cpp
|
|
777
|
+
|
|
778
|
+
runtime, state = fake_runtime
|
|
779
|
+
if mode == "compiled":
|
|
780
|
+
fn = runtime.PawFunction(_write_compiled_program(tmp_path), offline=True)
|
|
781
|
+
expected_prompt = list(b"payload:Suffix")
|
|
782
|
+
else:
|
|
783
|
+
_write_base_model("gpt2")
|
|
784
|
+
fn = paw.function(None, interpreter="gpt2")
|
|
785
|
+
expected_prompt = list(b"payload")
|
|
786
|
+
instance = state.instances[-1]
|
|
787
|
+
error = exception_type("processor failed")
|
|
788
|
+
later_calls = []
|
|
789
|
+
ignored_errors = []
|
|
790
|
+
native_scores = []
|
|
791
|
+
decoded_outputs = []
|
|
792
|
+
monkeypatch.setattr(sys, "unraisablehook", ignored_errors.append)
|
|
793
|
+
|
|
794
|
+
def failing_processor(input_ids, scores):
|
|
795
|
+
scores[:] = [float("-inf")] * len(scores)
|
|
796
|
+
if invalid_result:
|
|
797
|
+
# The native scores assignment must also be inside the guard.
|
|
798
|
+
return object()
|
|
799
|
+
raise error
|
|
800
|
+
|
|
801
|
+
def later_processor(input_ids, scores):
|
|
802
|
+
later_calls.append(list(input_ids))
|
|
803
|
+
return scores
|
|
804
|
+
|
|
805
|
+
def native_sample(*, temp, logits_processor=None):
|
|
806
|
+
if logits_processor is None:
|
|
807
|
+
return ord("A")
|
|
808
|
+
scores = [0.0] * 256
|
|
809
|
+
input_ids = list(instance.input_ids[:instance.n_tokens])
|
|
810
|
+
|
|
811
|
+
@ctypes.CFUNCTYPE(None)
|
|
812
|
+
def apply_processors():
|
|
813
|
+
nonlocal scores
|
|
814
|
+
for processor in logits_processor:
|
|
815
|
+
scores = processor(input_ids, scores)
|
|
816
|
+
|
|
817
|
+
apply_processors()
|
|
818
|
+
native_scores.append(list(scores))
|
|
819
|
+
# The guarded callback must leave native sampling usable, even when
|
|
820
|
+
# the failed processor has already invalidated every candidate.
|
|
821
|
+
assert len(scores) == 256 and all(math.isfinite(score) for score in scores)
|
|
822
|
+
return instance.token_eos()
|
|
823
|
+
|
|
824
|
+
def detokenize(tokens):
|
|
825
|
+
decoded_outputs.append(list(tokens))
|
|
826
|
+
return bytes(tokens)
|
|
827
|
+
|
|
828
|
+
monkeypatch.setattr(instance, "sample", native_sample)
|
|
829
|
+
monkeypatch.setattr(instance, "detokenize", detokenize)
|
|
830
|
+
eval_count = len(instance.eval_calls)
|
|
831
|
+
processors = llama_cpp.LogitsProcessorList([failing_processor, later_processor])
|
|
832
|
+
with pytest.raises(exception_type) as caught:
|
|
833
|
+
fn("payload", max_tokens=3, logits_processor=processors)
|
|
834
|
+
|
|
835
|
+
if not invalid_result:
|
|
836
|
+
assert caught.value is error
|
|
837
|
+
assert ignored_errors == []
|
|
838
|
+
assert later_calls == []
|
|
839
|
+
assert len(native_scores) == 1
|
|
840
|
+
assert instance.eval_calls[eval_count:] == [expected_prompt]
|
|
841
|
+
assert decoded_outputs == []
|
|
842
|
+
assert fn("payload", max_tokens=1) == "A"
|
|
843
|
+
assert len(native_scores) == 1
|
|
844
|
+
assert later_calls == ignored_errors == []
|
|
845
|
+
fn.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programasweights-0.4.5 → programasweights-0.4.6}/programasweights/runtime/interpreter_onnx.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|