logit-classifier 0.1.0__tar.gz → 0.2.1__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.
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/PKG-INFO +36 -14
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/README.md +35 -13
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/examples/compare_models.py +2 -1
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/examples/images.py +2 -1
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/examples/question_types.py +2 -1
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/examples/quickstart.py +2 -1
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/pyproject.toml +9 -2
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/__init__.py +3 -2
- logit_classifier-0.2.1/src/logit_classifier/_version.py +3 -0
- logit_classifier-0.2.1/src/logit_classifier/backends/_torch_window.py +96 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/backends/base.py +4 -0
- logit_classifier-0.2.1/src/logit_classifier/backends/comfy_clip.py +414 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/backends/hf.py +1 -83
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/classifier.py +34 -5
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/config.py +5 -4
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/prompt.py +8 -2
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/schema.py +14 -2
- logit_classifier-0.2.1/src/logit_classifier/tags.py +171 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/vision.py +55 -18
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/web/index.html +2 -2
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests/test_unit.py +1191 -7
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/.gitignore +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/FINDINGS.md +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/LICENSE +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/examples/http_client.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/examples/own_backend.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/__main__.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/backends/__init__.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/calibrate.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/cli.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/deps.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/errors.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/labels.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/py.typed +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/scoring.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/src/logit_classifier/service.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests/fixtures/banking77_test.json +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests/fixtures/eval_set.json +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests/fixtures/many_options_request.json +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests/fixtures/quickstart_request.json +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests/test_model.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_branch_packing.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_determinism_scope.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_env.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_math_sdp_reduction.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_multi_label.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_noul_wording.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/ab_temperature.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/banking77.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/benchmark.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/evaluate.py +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/_full.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/_sheet.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/low-L.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/low-R.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/mid-L.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/mid-R.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/top-L.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/inputs/top-R.png +0 -0
- {logit_classifier-0.1.0 → logit_classifier-0.2.1}/tests-AB/tune_groups.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: logit-classifier
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Local zero-shot classifier for text and images. Declares options, returns a calibrated probability for each, generates no text. Accepts the TypeSafe System One request format.
|
|
5
5
|
Project-URL: Repository, https://github.com/Blakeem/logit-classifier
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/Blakeem/logit-classifier/issues
|
|
@@ -88,8 +88,8 @@ beside your project instead, which is what the examples do.
|
|
|
88
88
|
Config(models_dir=Path("models"))
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
`LOGIT_MODELS_DIR` sets the same folder for
|
|
92
|
-
Hugging Face cache itself.
|
|
91
|
+
`LOGIT_MODELS_DIR` sets the same folder for the service and for `Config.from_env()`.
|
|
92
|
+
`HF_HOME` moves the Hugging Face cache itself.
|
|
93
93
|
|
|
94
94
|
## Models
|
|
95
95
|
|
|
@@ -98,8 +98,9 @@ Hugging Face cache itself.
|
|
|
98
98
|
| reads images | yes | no |
|
|
99
99
|
| better at | choice and score | noul |
|
|
100
100
|
|
|
101
|
-
`Qwen3-VL-4B-Instruct` is the default. Set `LOGIT_MODEL_ID` to use the other one
|
|
102
|
-
chat model loads, and a model with no
|
|
101
|
+
`Qwen3-VL-4B-Instruct` is the default. Set `LOGIT_MODEL_ID` to use the other one in the
|
|
102
|
+
service or through `Config.from_env()`. Any Qwen chat model loads, and a model with no
|
|
103
|
+
fitted temperature gets 2.5.
|
|
103
104
|
|
|
104
105
|
## Python API
|
|
105
106
|
|
|
@@ -169,7 +170,7 @@ the model.
|
|
|
169
170
|
|
|
170
171
|
```json
|
|
171
172
|
{
|
|
172
|
-
"model": "logit-classifier-0.1
|
|
173
|
+
"model": "logit-classifier-0.2.1",
|
|
173
174
|
"answers": {
|
|
174
175
|
"department": {
|
|
175
176
|
"type": "choice",
|
|
@@ -210,7 +211,7 @@ only in process.
|
|
|
210
211
|
"questions": { "sky": { "type": "noul", "instructions": "This crop shows the night sky" } } }
|
|
211
212
|
```
|
|
212
213
|
|
|
213
|
-
The image is encoded once for the entire request
|
|
214
|
+
The image is encoded once for the entire request. So asking several questions about one
|
|
214
215
|
picture costs little more than asking one.
|
|
215
216
|
|
|
216
217
|
A model with no vision tower rejects the request rather than ignoring the picture.
|
|
@@ -230,8 +231,9 @@ Each script runs on its own.
|
|
|
230
231
|
|
|
231
232
|
## Configuration
|
|
232
233
|
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
The service and `logit-classifier config` read these variables at startup through
|
|
235
|
+
`Config.from_env()`. Library code gets them by calling `Config.from_env()`, since `Config()`
|
|
236
|
+
reads none of them. `logit-classifier config` prints what they produce.
|
|
235
237
|
|
|
236
238
|
| Variable | Default | Effect |
|
|
237
239
|
|---|---|---|
|
|
@@ -242,6 +244,7 @@ they produce.
|
|
|
242
244
|
| `LOGIT_PRIOR_DEBIAS` | `1` | set to `0` to skip the label prior |
|
|
243
245
|
| `LOGIT_BATCH_BRANCHES` | `1` | set to `0` for one forward pass per branch |
|
|
244
246
|
| `LOGIT_SCORE_METHOD` | `joint` | set to `independent` to judge each level alone |
|
|
247
|
+
| `LOGIT_PERMUTATIONS` | `1` | letterings averaged per question, each one adds branches |
|
|
245
248
|
| `LOGIT_ABSTAIN` | `1` | set to `0` to drop the `none of these` label below 52 options |
|
|
246
249
|
| `LOGIT_CALIBRATION_PATH` | `calibration.json` | where the service stores the running prior |
|
|
247
250
|
|
|
@@ -269,9 +272,10 @@ Nothing is sampled, so the same request returns bitwise identical logits.
|
|
|
269
272
|
|
|
270
273
|
Batch composition and padding length both change the low bits of a bfloat16 forward pass,
|
|
271
274
|
and both are pure functions of the request. So the same question asked inside two
|
|
272
|
-
different requests can differ slightly.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
different requests can differ slightly. Scoring each branch alone makes a question
|
|
276
|
+
independent of the questions sent with it and costs one forward pass per branch. The service
|
|
277
|
+
turns it on with `LOGIT_BATCH_BRANCHES=0`. Library code passes `Config(batch_branches=False)`,
|
|
278
|
+
and `ComfyClipBackend` takes `batch_branches=False` as a keyword.
|
|
275
279
|
|
|
276
280
|
A forward pass needs several process-global torch settings held at known values. Torch
|
|
277
281
|
exposes none of them as a call argument, so the backend sets them around each pass and
|
|
@@ -320,6 +324,24 @@ the render and label contracts and returns one token id per label.
|
|
|
320
324
|
`logit_classifier.backends.hf.HFBackend` is the transformers implementation to read
|
|
321
325
|
against.
|
|
322
326
|
|
|
327
|
+
`ComfyClipBackend` is the backend over a Qwen3-VL text encoder that the workflow already
|
|
328
|
+
loaded, such as the one Krea 2 uses. So no second model is loaded into VRAM.
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
from logit_classifier import Classifier, Config, NoulQuestion, SystemOneRequest
|
|
332
|
+
from logit_classifier.backends.comfy_clip import ComfyClipBackend
|
|
333
|
+
|
|
334
|
+
classifier = Classifier(Config(), backend=ComfyClipBackend(clip))
|
|
335
|
+
question = NoulQuestion(instructions="This image visibly contains a dragon")
|
|
336
|
+
request = SystemOneRequest(state="", questions={"dragon": question})
|
|
337
|
+
response, _ = classifier.classify(request, image=image)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The `image` keyword takes an image the host already decoded, such as a ComfyUI IMAGE of
|
|
341
|
+
shape `[1, H, W, 3]`. An empty state asks about the image alone. Questions share one forward
|
|
342
|
+
pass of up to 4,096 tokens, and the image counts toward that limit. Any text encoder other
|
|
343
|
+
than Qwen3-VL raises `UnsupportedModelError`.
|
|
344
|
+
|
|
323
345
|
## Differences From Jev
|
|
324
346
|
|
|
325
347
|
This project matches the System One request and response format and the documented limits.
|
|
@@ -329,8 +351,8 @@ Jev is trained for calibrated probabilities. This project reads them from a gene
|
|
|
329
351
|
so the choices agree more often than the confidences do.
|
|
330
352
|
|
|
331
353
|
Jev judges a score level without its number or its neighbours. This project judges all
|
|
332
|
-
levels together by default.
|
|
333
|
-
|
|
354
|
+
levels together by default. The service follows the documented behavior with
|
|
355
|
+
`LOGIT_SCORE_METHOD=independent`, and library code with `Config(score_method="independent")`.
|
|
334
356
|
|
|
335
357
|
Jev publishes status codes but no error body. The error shape here is our own.
|
|
336
358
|
|
|
@@ -55,8 +55,8 @@ beside your project instead, which is what the examples do.
|
|
|
55
55
|
Config(models_dir=Path("models"))
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
`LOGIT_MODELS_DIR` sets the same folder for
|
|
59
|
-
Hugging Face cache itself.
|
|
58
|
+
`LOGIT_MODELS_DIR` sets the same folder for the service and for `Config.from_env()`.
|
|
59
|
+
`HF_HOME` moves the Hugging Face cache itself.
|
|
60
60
|
|
|
61
61
|
## Models
|
|
62
62
|
|
|
@@ -65,8 +65,9 @@ Hugging Face cache itself.
|
|
|
65
65
|
| reads images | yes | no |
|
|
66
66
|
| better at | choice and score | noul |
|
|
67
67
|
|
|
68
|
-
`Qwen3-VL-4B-Instruct` is the default. Set `LOGIT_MODEL_ID` to use the other one
|
|
69
|
-
chat model loads, and a model with no
|
|
68
|
+
`Qwen3-VL-4B-Instruct` is the default. Set `LOGIT_MODEL_ID` to use the other one in the
|
|
69
|
+
service or through `Config.from_env()`. Any Qwen chat model loads, and a model with no
|
|
70
|
+
fitted temperature gets 2.5.
|
|
70
71
|
|
|
71
72
|
## Python API
|
|
72
73
|
|
|
@@ -136,7 +137,7 @@ the model.
|
|
|
136
137
|
|
|
137
138
|
```json
|
|
138
139
|
{
|
|
139
|
-
"model": "logit-classifier-0.1
|
|
140
|
+
"model": "logit-classifier-0.2.1",
|
|
140
141
|
"answers": {
|
|
141
142
|
"department": {
|
|
142
143
|
"type": "choice",
|
|
@@ -177,7 +178,7 @@ only in process.
|
|
|
177
178
|
"questions": { "sky": { "type": "noul", "instructions": "This crop shows the night sky" } } }
|
|
178
179
|
```
|
|
179
180
|
|
|
180
|
-
The image is encoded once for the entire request
|
|
181
|
+
The image is encoded once for the entire request. So asking several questions about one
|
|
181
182
|
picture costs little more than asking one.
|
|
182
183
|
|
|
183
184
|
A model with no vision tower rejects the request rather than ignoring the picture.
|
|
@@ -197,8 +198,9 @@ Each script runs on its own.
|
|
|
197
198
|
|
|
198
199
|
## Configuration
|
|
199
200
|
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
The service and `logit-classifier config` read these variables at startup through
|
|
202
|
+
`Config.from_env()`. Library code gets them by calling `Config.from_env()`, since `Config()`
|
|
203
|
+
reads none of them. `logit-classifier config` prints what they produce.
|
|
202
204
|
|
|
203
205
|
| Variable | Default | Effect |
|
|
204
206
|
|---|---|---|
|
|
@@ -209,6 +211,7 @@ they produce.
|
|
|
209
211
|
| `LOGIT_PRIOR_DEBIAS` | `1` | set to `0` to skip the label prior |
|
|
210
212
|
| `LOGIT_BATCH_BRANCHES` | `1` | set to `0` for one forward pass per branch |
|
|
211
213
|
| `LOGIT_SCORE_METHOD` | `joint` | set to `independent` to judge each level alone |
|
|
214
|
+
| `LOGIT_PERMUTATIONS` | `1` | letterings averaged per question, each one adds branches |
|
|
212
215
|
| `LOGIT_ABSTAIN` | `1` | set to `0` to drop the `none of these` label below 52 options |
|
|
213
216
|
| `LOGIT_CALIBRATION_PATH` | `calibration.json` | where the service stores the running prior |
|
|
214
217
|
|
|
@@ -236,9 +239,10 @@ Nothing is sampled, so the same request returns bitwise identical logits.
|
|
|
236
239
|
|
|
237
240
|
Batch composition and padding length both change the low bits of a bfloat16 forward pass,
|
|
238
241
|
and both are pure functions of the request. So the same question asked inside two
|
|
239
|
-
different requests can differ slightly.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
different requests can differ slightly. Scoring each branch alone makes a question
|
|
243
|
+
independent of the questions sent with it and costs one forward pass per branch. The service
|
|
244
|
+
turns it on with `LOGIT_BATCH_BRANCHES=0`. Library code passes `Config(batch_branches=False)`,
|
|
245
|
+
and `ComfyClipBackend` takes `batch_branches=False` as a keyword.
|
|
242
246
|
|
|
243
247
|
A forward pass needs several process-global torch settings held at known values. Torch
|
|
244
248
|
exposes none of them as a call argument, so the backend sets them around each pass and
|
|
@@ -287,6 +291,24 @@ the render and label contracts and returns one token id per label.
|
|
|
287
291
|
`logit_classifier.backends.hf.HFBackend` is the transformers implementation to read
|
|
288
292
|
against.
|
|
289
293
|
|
|
294
|
+
`ComfyClipBackend` is the backend over a Qwen3-VL text encoder that the workflow already
|
|
295
|
+
loaded, such as the one Krea 2 uses. So no second model is loaded into VRAM.
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from logit_classifier import Classifier, Config, NoulQuestion, SystemOneRequest
|
|
299
|
+
from logit_classifier.backends.comfy_clip import ComfyClipBackend
|
|
300
|
+
|
|
301
|
+
classifier = Classifier(Config(), backend=ComfyClipBackend(clip))
|
|
302
|
+
question = NoulQuestion(instructions="This image visibly contains a dragon")
|
|
303
|
+
request = SystemOneRequest(state="", questions={"dragon": question})
|
|
304
|
+
response, _ = classifier.classify(request, image=image)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The `image` keyword takes an image the host already decoded, such as a ComfyUI IMAGE of
|
|
308
|
+
shape `[1, H, W, 3]`. An empty state asks about the image alone. Questions share one forward
|
|
309
|
+
pass of up to 4,096 tokens, and the image counts toward that limit. Any text encoder other
|
|
310
|
+
than Qwen3-VL raises `UnsupportedModelError`.
|
|
311
|
+
|
|
290
312
|
## Differences From Jev
|
|
291
313
|
|
|
292
314
|
This project matches the System One request and response format and the documented limits.
|
|
@@ -296,8 +318,8 @@ Jev is trained for calibrated probabilities. This project reads them from a gene
|
|
|
296
318
|
so the choices agree more often than the confidences do.
|
|
297
319
|
|
|
298
320
|
Jev judges a score level without its number or its neighbours. This project judges all
|
|
299
|
-
levels together by default.
|
|
300
|
-
|
|
321
|
+
levels together by default. The service follows the documented behavior with
|
|
322
|
+
`LOGIT_SCORE_METHOD=independent`, and library code with `Config(score_method="independent")`.
|
|
301
323
|
|
|
302
324
|
Jev publishes status codes but no error body. The error shape here is our own.
|
|
303
325
|
|
|
@@ -19,7 +19,8 @@ from pathlib import Path
|
|
|
19
19
|
from logit_classifier import Classifier, Config, load_model, parse_request
|
|
20
20
|
|
|
21
21
|
# Weights land beside the project instead of in the global Hugging Face cache.
|
|
22
|
-
#
|
|
22
|
+
# Edit MODELS_DIR to share one folder across projects.
|
|
23
|
+
# Neither LOGIT_MODELS_DIR nor HF_HOME reaches this script.
|
|
23
24
|
MODELS_DIR = Path(__file__).resolve().parent.parent / "models"
|
|
24
25
|
|
|
25
26
|
MODELS = ["Qwen/Qwen3-VL-4B-Instruct", "Qwen/Qwen3-4B-Instruct-2507"]
|
|
@@ -23,7 +23,8 @@ from logit_classifier import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
# Weights land beside the project instead of in the global Hugging Face cache.
|
|
26
|
-
#
|
|
26
|
+
# Edit MODELS_DIR to share one folder across projects.
|
|
27
|
+
# Neither LOGIT_MODELS_DIR nor HF_HOME reaches this script.
|
|
27
28
|
MODELS_DIR = Path(__file__).resolve().parent.parent / "models"
|
|
28
29
|
QUESTIONS = {
|
|
29
30
|
"subject": {
|
|
@@ -14,7 +14,8 @@ from pathlib import Path
|
|
|
14
14
|
from logit_classifier import Classifier, Config, load_model, parse_request
|
|
15
15
|
|
|
16
16
|
# Weights land beside the project instead of in the global Hugging Face cache.
|
|
17
|
-
#
|
|
17
|
+
# Edit MODELS_DIR to share one folder across projects.
|
|
18
|
+
# Neither LOGIT_MODELS_DIR nor HF_HOME reaches this script.
|
|
18
19
|
MODELS_DIR = Path(__file__).resolve().parent.parent / "models"
|
|
19
20
|
|
|
20
21
|
REVIEW = "Shipped two days late and the box was crushed, but the product itself works fine."
|
|
@@ -16,7 +16,8 @@ from pathlib import Path
|
|
|
16
16
|
from logit_classifier import Classifier, Config, load_model, parse_request
|
|
17
17
|
|
|
18
18
|
# Weights land beside the project instead of in the global Hugging Face cache.
|
|
19
|
-
#
|
|
19
|
+
# Edit MODELS_DIR to share one folder across projects.
|
|
20
|
+
# Neither LOGIT_MODELS_DIR nor HF_HOME reaches this script.
|
|
20
21
|
MODELS_DIR = Path(__file__).resolve().parent.parent / "models"
|
|
21
22
|
|
|
22
23
|
SAMPLE_TEXT = "I have been trying to connect my Stripe account for 3 days and it keeps failing."
|
|
@@ -136,7 +136,8 @@ show_error_codes = true
|
|
|
136
136
|
[[tool.mypy.overrides]]
|
|
137
137
|
# transformers publishes no complete stubs, and accelerate none at all. torch is
|
|
138
138
|
# listed so the type gate still runs where the hf extra is absent, such as CI.
|
|
139
|
-
|
|
139
|
+
# The ComfyUI host supplies comfy, and neither this venv nor CI has it.
|
|
140
|
+
module = ["transformers.*", "accelerate.*", "torch.*", "comfy.*"]
|
|
140
141
|
ignore_missing_imports = true
|
|
141
142
|
|
|
142
143
|
[[tool.mypy.overrides]]
|
|
@@ -148,6 +149,12 @@ module = ["logit_classifier.backends.hf"]
|
|
|
148
149
|
disallow_untyped_calls = false
|
|
149
150
|
disallow_untyped_decorators = false
|
|
150
151
|
|
|
152
|
+
[[tool.mypy.overrides]]
|
|
153
|
+
# torch leaves fp16_bf16_reduction_math_sdp_allowed unannotated, and a line ignore
|
|
154
|
+
# would go unused, and so fail, where the hf extra is absent and torch is Any.
|
|
155
|
+
module = ["logit_classifier.backends._torch_window"]
|
|
156
|
+
disallow_untyped_calls = false
|
|
157
|
+
|
|
151
158
|
[tool.pytest.ini_options]
|
|
152
159
|
pythonpath = ["src"]
|
|
153
160
|
testpaths = ["tests"]
|
|
@@ -159,7 +166,7 @@ requires = ["hatchling"]
|
|
|
159
166
|
build-backend = "hatchling.build"
|
|
160
167
|
|
|
161
168
|
[tool.hatch.version]
|
|
162
|
-
path = "src/logit_classifier/
|
|
169
|
+
path = "src/logit_classifier/_version.py"
|
|
163
170
|
|
|
164
171
|
[tool.hatch.build.targets.wheel]
|
|
165
172
|
packages = ["src/logit_classifier"]
|
|
@@ -9,10 +9,12 @@ with the `[hf]` extra and the HTTP service with `[service]`.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
from ._version import __version__
|
|
12
13
|
from .backends.base import (
|
|
13
14
|
Backend,
|
|
14
15
|
BackendContractError,
|
|
15
16
|
BranchLogits,
|
|
17
|
+
UnsupportedModelError,
|
|
16
18
|
VisionUnsupportedError,
|
|
17
19
|
verify_backend,
|
|
18
20
|
)
|
|
@@ -45,8 +47,6 @@ from .schema import (
|
|
|
45
47
|
)
|
|
46
48
|
from .vision import ImageError
|
|
47
49
|
|
|
48
|
-
__version__ = "0.1.0"
|
|
49
|
-
|
|
50
50
|
# The ComfyUI socket type a node pack declares for a loaded classifier. It lives
|
|
51
51
|
# here so the library and the packs cannot drift apart on the spelling.
|
|
52
52
|
COMFY_SOCKET_TYPE = "LOGIT_CLASSIFIER"
|
|
@@ -79,6 +79,7 @@ __all__ = [
|
|
|
79
79
|
"ScoreQuestion",
|
|
80
80
|
"SystemOneRequest",
|
|
81
81
|
"SystemOneResponse",
|
|
82
|
+
"UnsupportedModelError",
|
|
82
83
|
"Usage",
|
|
83
84
|
"VisionUnsupportedError",
|
|
84
85
|
"__version__",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""The determinism window every backend's forward pass runs inside.
|
|
2
|
+
|
|
3
|
+
torch is imported inside the window rather than at module scope, so a backend over a
|
|
4
|
+
host's own model can import this without pulling torch into a core-only process.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import threading
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
# Two windows open on two threads would interleave their saves, and the second to
|
|
15
|
+
# exit would restore the pinned values rather than the host's. The service already
|
|
16
|
+
# serialises on its own GPU lock, which is always taken before this one.
|
|
17
|
+
_WINDOW = threading.RLock()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@contextmanager
|
|
21
|
+
def _determinism() -> Iterator[None]:
|
|
22
|
+
"""Hold the torch globals that decide bit-exact reductions, for one forward pass.
|
|
23
|
+
|
|
24
|
+
Every alternative value buys speed by giving up bit-exactness, so none is tunable.
|
|
25
|
+
torch exposes none of them as a call argument, so scoping them means setting them
|
|
26
|
+
here and putting the host's values back after. A ComfyUI host sharing this process
|
|
27
|
+
keeps its own settings everywhere outside the block.
|
|
28
|
+
|
|
29
|
+
On the CUDA attention path only allow_bf16_reduced_precision_reduction moves a logit
|
|
30
|
+
on either shipped model. The rest are kept because cudnn picks convolution algorithms
|
|
31
|
+
by timing, which is specific to the card, and `ab_determinism_scope.py` measured one.
|
|
32
|
+
|
|
33
|
+
The sdp and fp16 accumulation settings are held for a third reason, that ComfyUI turns
|
|
34
|
+
each of them on and neither is reachable by that sweep. `ab_math_sdp_reduction.py`
|
|
35
|
+
measures the sdp one on the math backend.
|
|
36
|
+
|
|
37
|
+
Where torch has per-backend matmul slots, the coarse getter raises once a host has
|
|
38
|
+
set a slot directly, so only the raw slots are saved, pinned and put back there.
|
|
39
|
+
"""
|
|
40
|
+
import torch
|
|
41
|
+
|
|
42
|
+
with _WINDOW:
|
|
43
|
+
saved_benchmark = torch.backends.cudnn.benchmark
|
|
44
|
+
saved_deterministic = torch.backends.cudnn.deterministic
|
|
45
|
+
saved_bf16 = torch.backends.cuda.matmul.allow_bf16_reduced_precision_reduction
|
|
46
|
+
# torch ships no stub for the mkldnn matmul slot, so it is reached by name.
|
|
47
|
+
mkldnn: Any = getattr(torch.backends.mkldnn, "matmul", None)
|
|
48
|
+
has_slots = hasattr(torch.backends.cuda.matmul, "fp32_precision") and hasattr(
|
|
49
|
+
mkldnn, "fp32_precision"
|
|
50
|
+
)
|
|
51
|
+
saved_matmul = None if has_slots else torch.get_float32_matmul_precision()
|
|
52
|
+
saved_cuda = torch.backends.cuda.matmul.fp32_precision if has_slots else None
|
|
53
|
+
saved_mkldnn = mkldnn.fp32_precision if has_slots else None
|
|
54
|
+
# ComfyUI turns this on at import, at comfy/model_management.py:569. It governs
|
|
55
|
+
# the math attention backend, which runs only when attention_backends is empty.
|
|
56
|
+
has_sdp = hasattr(torch.backends.cuda, "allow_fp16_bf16_reduction_math_sdp") and hasattr(
|
|
57
|
+
torch.backends.cuda, "fp16_bf16_reduction_math_sdp_allowed"
|
|
58
|
+
)
|
|
59
|
+
saved_sdp = bool(
|
|
60
|
+
has_sdp and torch.backends.cuda.fp16_bf16_reduction_math_sdp_allowed()
|
|
61
|
+
)
|
|
62
|
+
# A bare --fast turns this on, since comfy/cli_args.py then enables every
|
|
63
|
+
# PerformanceFeature. It is the fp16 sibling of the bf16 reduction above.
|
|
64
|
+
has_fp16_acc = hasattr(torch.backends.cuda.matmul, "allow_fp16_accumulation")
|
|
65
|
+
saved_fp16_acc = bool(
|
|
66
|
+
has_fp16_acc and torch.backends.cuda.matmul.allow_fp16_accumulation
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Pinning sits inside the try, so a setter that raises part way still restores the host.
|
|
70
|
+
try:
|
|
71
|
+
torch.backends.cudnn.benchmark = False
|
|
72
|
+
torch.backends.cudnn.deterministic = True
|
|
73
|
+
torch.backends.cuda.matmul.allow_bf16_reduced_precision_reduction = False
|
|
74
|
+
if has_slots:
|
|
75
|
+
torch.backends.cuda.matmul.fp32_precision = "ieee"
|
|
76
|
+
mkldnn.fp32_precision = "ieee"
|
|
77
|
+
else:
|
|
78
|
+
torch.set_float32_matmul_precision("highest")
|
|
79
|
+
if has_sdp:
|
|
80
|
+
torch.backends.cuda.allow_fp16_bf16_reduction_math_sdp(False)
|
|
81
|
+
if has_fp16_acc:
|
|
82
|
+
torch.backends.cuda.matmul.allow_fp16_accumulation = False
|
|
83
|
+
yield
|
|
84
|
+
finally:
|
|
85
|
+
torch.backends.cudnn.benchmark = saved_benchmark
|
|
86
|
+
torch.backends.cudnn.deterministic = saved_deterministic
|
|
87
|
+
torch.backends.cuda.matmul.allow_bf16_reduced_precision_reduction = saved_bf16
|
|
88
|
+
if has_slots:
|
|
89
|
+
torch.backends.cuda.matmul.fp32_precision = saved_cuda
|
|
90
|
+
mkldnn.fp32_precision = saved_mkldnn
|
|
91
|
+
elif saved_matmul is not None:
|
|
92
|
+
torch.set_float32_matmul_precision(saved_matmul)
|
|
93
|
+
if has_sdp:
|
|
94
|
+
torch.backends.cuda.allow_fp16_bf16_reduction_math_sdp(saved_sdp)
|
|
95
|
+
if has_fp16_acc:
|
|
96
|
+
torch.backends.cuda.matmul.allow_fp16_accumulation = saved_fp16_acc
|
|
@@ -32,6 +32,10 @@ class BackendContractError(LogitClassifierError, RuntimeError):
|
|
|
32
32
|
"""A backend returned rows the port does not allow."""
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class UnsupportedModelError(LogitClassifierError, ValueError):
|
|
36
|
+
"""A host passed a loaded model this backend cannot read a logit row from."""
|
|
37
|
+
|
|
38
|
+
|
|
35
39
|
@dataclass(frozen=True)
|
|
36
40
|
class BranchLogits:
|
|
37
41
|
"""Raw label logits for one branch, before any calibration."""
|