spinf-serve 0.1.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.
Files changed (145) hide show
  1. spinf_serve-0.1.1/.github/workflows/ci.yml +29 -0
  2. spinf_serve-0.1.1/.gitignore +10 -0
  3. spinf_serve-0.1.1/.python-version +1 -0
  4. spinf_serve-0.1.1/ACKNOWLEDGEMENTS.md +57 -0
  5. spinf_serve-0.1.1/LICENSE +202 -0
  6. spinf_serve-0.1.1/NOTICE +11 -0
  7. spinf_serve-0.1.1/PKG-INFO +240 -0
  8. spinf_serve-0.1.1/README.md +207 -0
  9. spinf_serve-0.1.1/benchmarks/README.md +53 -0
  10. spinf_serve-0.1.1/benchmarks/compare.py +168 -0
  11. spinf_serve-0.1.1/benchmarks/wikinews_fixture.py +235 -0
  12. spinf_serve-0.1.1/docs/ARCHITECTURE.md +194 -0
  13. spinf_serve-0.1.1/docs/BATCH_INVARIANCE.md +161 -0
  14. spinf_serve-0.1.1/docs/EXTRACTION.md +179 -0
  15. spinf_serve-0.1.1/docs/PRECISION.md +162 -0
  16. spinf_serve-0.1.1/docs/QWEN.md +492 -0
  17. spinf_serve-0.1.1/docs/README.md +61 -0
  18. spinf_serve-0.1.1/docs/calibration.md +113 -0
  19. spinf_serve-0.1.1/docs/configuration.md +133 -0
  20. spinf_serve-0.1.1/docs/examples.md +278 -0
  21. spinf_serve-0.1.1/docs/faq.md +56 -0
  22. spinf_serve-0.1.1/docs/guides/aspect-based-sentiment-analysis.md +95 -0
  23. spinf_serve-0.1.1/docs/guides/custom-content-moderation.md +117 -0
  24. spinf_serve-0.1.1/docs/guides/decision-models.md +102 -0
  25. spinf_serve-0.1.1/docs/guides/entity-sentiment-analysis.md +98 -0
  26. spinf_serve-0.1.1/docs/guides/index.md +39 -0
  27. spinf_serve-0.1.1/docs/guides/intent-classification.md +119 -0
  28. spinf_serve-0.1.1/docs/guides/llm-probabilities.md +97 -0
  29. spinf_serve-0.1.1/docs/guides/no-output-tokens.md +81 -0
  30. spinf_serve-0.1.1/docs/guides/open-ended-survey-coding.md +93 -0
  31. spinf_serve-0.1.1/docs/guides/probabilistic-if.md +101 -0
  32. spinf_serve-0.1.1/docs/guides/zero-shot-few-shot-classification.md +95 -0
  33. spinf_serve-0.1.1/docs/prompt-packs.md +212 -0
  34. spinf_serve-0.1.1/docs/quickstart.md +171 -0
  35. spinf_serve-0.1.1/docs/scoring-api.md +267 -0
  36. spinf_serve-0.1.1/docs/writing-questions.md +155 -0
  37. spinf_serve-0.1.1/fixtures/README.md +41 -0
  38. spinf_serve-0.1.1/fixtures/wikinews_20261024.json.gz +0 -0
  39. spinf_serve-0.1.1/fixtures/wikinews_20261024.spinf-gemma-4-12b-fp8.json.gz +0 -0
  40. spinf_serve-0.1.1/fixtures/wikinews_20261024.spinf-gemma-4-31b-fp8.json.gz +0 -0
  41. spinf_serve-0.1.1/fixtures/wikinews_20261024.spinf-qwen3.8-27b-int4.json.gz +0 -0
  42. spinf_serve-0.1.1/pyproject.toml +59 -0
  43. spinf_serve-0.1.1/quantizations/README.md +38 -0
  44. spinf_serve-0.1.1/quantizations/export_outliers.py +144 -0
  45. spinf_serve-0.1.1/quantizations/fp8_quantize.py +232 -0
  46. spinf_serve-0.1.1/quantizations/gemma-4-12b-fp8/MODEL_CARD.md +82 -0
  47. spinf_serve-0.1.1/quantizations/gemma-4-12b-fp8/calibration.json +1024 -0
  48. spinf_serve-0.1.1/quantizations/gemma-4-12b-fp8/recipe.yaml +20 -0
  49. spinf_serve-0.1.1/quantizations/gemma-4-31b-fp8/MODEL_CARD.md +80 -0
  50. spinf_serve-0.1.1/quantizations/gemma-4-31b-fp8/calibration.json +1270 -0
  51. spinf_serve-0.1.1/quantizations/gemma-4-31b-fp8/recipe.yaml +21 -0
  52. spinf_serve-0.1.1/quantizations/gptq_quantize.py +279 -0
  53. spinf_serve-0.1.1/quantizations/pplx-decider-v1-27b-int4/MODEL_CARD.md +98 -0
  54. spinf_serve-0.1.1/quantizations/pplx-decider-v1-27b-int4/quantize_config.json +48 -0
  55. spinf_serve-0.1.1/quantizations/pplx-decider-v1-27b-int4/recipe.yaml +24 -0
  56. spinf_serve-0.1.1/quantizations/pplx-decider-v1-27b-int4/spinf_outliers.json +1 -0
  57. spinf_serve-0.1.1/quantizations/qwen3.8-27b-int4/MODEL_CARD.md +83 -0
  58. spinf_serve-0.1.1/quantizations/qwen3.8-27b-int4/recipe.yaml +25 -0
  59. spinf_serve-0.1.1/quantizations/qwen3.8-27b-int4/spinf_outliers.json +1 -0
  60. spinf_serve-0.1.1/spinf_serve/__init__.py +5 -0
  61. spinf_serve-0.1.1/spinf_serve/__main__.py +33 -0
  62. spinf_serve-0.1.1/spinf_serve/cli.py +191 -0
  63. spinf_serve-0.1.1/spinf_serve/compiler.py +656 -0
  64. spinf_serve-0.1.1/spinf_serve/config.py +119 -0
  65. spinf_serve-0.1.1/spinf_serve/decode.py +402 -0
  66. spinf_serve-0.1.1/spinf_serve/extract.py +268 -0
  67. spinf_serve-0.1.1/spinf_serve/manifests/__init__.py +135 -0
  68. spinf_serve-0.1.1/spinf_serve/manifests/gemma-4-12b-fp8.toml +24 -0
  69. spinf_serve-0.1.1/spinf_serve/manifests/gemma-4-31b-fp8.toml +24 -0
  70. spinf_serve-0.1.1/spinf_serve/manifests/pplx-decider-v1-27b-int4.toml +23 -0
  71. spinf_serve-0.1.1/spinf_serve/manifests/qwen3.8-27b-int4.toml +24 -0
  72. spinf_serve-0.1.1/spinf_serve/media.py +153 -0
  73. spinf_serve-0.1.1/spinf_serve/models/__init__.py +1 -0
  74. spinf_serve-0.1.1/spinf_serve/models/base.py +88 -0
  75. spinf_serve-0.1.1/spinf_serve/models/gemma4.py +435 -0
  76. spinf_serve-0.1.1/spinf_serve/models/qwen35/__init__.py +1 -0
  77. spinf_serve-0.1.1/spinf_serve/models/qwen35/gdn.py +235 -0
  78. spinf_serve-0.1.1/spinf_serve/models/qwen35/qwen.py +1165 -0
  79. spinf_serve-0.1.1/spinf_serve/models/qwen35/qwen_decode.py +349 -0
  80. spinf_serve-0.1.1/spinf_serve/models/qwen35/qwen_vision.py +195 -0
  81. spinf_serve-0.1.1/spinf_serve/models/registry.py +25 -0
  82. spinf_serve-0.1.1/spinf_serve/options.py +27 -0
  83. spinf_serve-0.1.1/spinf_serve/packer.py +491 -0
  84. spinf_serve-0.1.1/spinf_serve/packs/__init__.py +454 -0
  85. spinf_serve-0.1.1/spinf_serve/packs/__main__.py +87 -0
  86. spinf_serve-0.1.1/spinf_serve/packs/catalog.py +316 -0
  87. spinf_serve-0.1.1/spinf_serve/packs/pipeline.py +234 -0
  88. spinf_serve-0.1.1/spinf_serve/packs/refs.py +178 -0
  89. spinf_serve-0.1.1/spinf_serve/packs/sources.py +192 -0
  90. spinf_serve-0.1.1/spinf_serve/parity.py +89 -0
  91. spinf_serve-0.1.1/spinf_serve/protocol.py +410 -0
  92. spinf_serve-0.1.1/spinf_serve/quant/__init__.py +6 -0
  93. spinf_serve-0.1.1/spinf_serve/quant/calibrate.py +217 -0
  94. spinf_serve-0.1.1/spinf_serve/quant/fp8.py +72 -0
  95. spinf_serve-0.1.1/spinf_serve/quant/i8gemm.py +407 -0
  96. spinf_serve-0.1.1/spinf_serve/quant/linears.py +135 -0
  97. spinf_serve-0.1.1/spinf_serve/quant/tables/btbtyler09__Qwen3.8-27B-GPTQ-4bit/b64f44dfedb71b73287219e0ec348b0a20c447e2-outliers.json +1 -0
  98. spinf_serve-0.1.1/spinf_serve/quant/tables/google__gemma-4-12B/023679ed352de9bb66cc873c9009ce3482585c08.json +1024 -0
  99. spinf_serve-0.1.1/spinf_serve/quant/tables/google__gemma-4-31B/5bbc2fb1c1b2c611d06e3d9f23c170ba21659d89.json +1270 -0
  100. spinf_serve-0.1.1/spinf_serve/quant/tables.py +51 -0
  101. spinf_serve-0.1.1/spinf_serve/quant/w4gemm.py +66 -0
  102. spinf_serve-0.1.1/spinf_serve/scorer.py +891 -0
  103. spinf_serve-0.1.1/spinf_serve/server/__init__.py +1 -0
  104. spinf_serve-0.1.1/spinf_serve/server/__main__.py +8 -0
  105. spinf_serve-0.1.1/spinf_serve/server/api.py +410 -0
  106. spinf_serve-0.1.1/spinf_serve/server/app.py +272 -0
  107. spinf_serve-0.1.1/spinf_serve/server/config.py +232 -0
  108. spinf_serve-0.1.1/spinf_serve/service.py +368 -0
  109. spinf_serve-0.1.1/spinf_serve/weights.py +105 -0
  110. spinf_serve-0.1.1/tests/conformance/all_combinations.json +38 -0
  111. spinf_serve-0.1.1/tests/conformance/case_and_floor_off.json +38 -0
  112. spinf_serve-0.1.1/tests/conformance/chat_completions.json +27 -0
  113. spinf_serve-0.1.1/tests/conformance/err_duplicate_id.json +36 -0
  114. spinf_serve-0.1.1/tests/conformance/err_invalid_template.json +26 -0
  115. spinf_serve-0.1.1/tests/conformance/err_missing_value.json +26 -0
  116. spinf_serve-0.1.1/tests/conformance/err_option_boundary.json +27 -0
  117. spinf_serve-0.1.1/tests/conformance/err_pack_media.json +33 -0
  118. spinf_serve-0.1.1/tests/conformance/err_scoring_required.json +20 -0
  119. spinf_serve-0.1.1/tests/conformance/err_unknown_pack.json +22 -0
  120. spinf_serve-0.1.1/tests/conformance/err_unknown_query_id.json +35 -0
  121. spinf_serve-0.1.1/tests/conformance/extraction.json +37 -0
  122. spinf_serve-0.1.1/tests/conformance/generation.json +24 -0
  123. spinf_serve-0.1.1/tests/conformance/many_inputs.json +84 -0
  124. spinf_serve-0.1.1/tests/conformance/one_content.json +51 -0
  125. spinf_serve-0.1.1/tests/conformance/pack_included_queries.json +38 -0
  126. spinf_serve-0.1.1/tests/conformance/pack_moderation.json +38 -0
  127. spinf_serve-0.1.1/tests/conformance.py +164 -0
  128. spinf_serve-0.1.1/tests/test_config.py +26 -0
  129. spinf_serve-0.1.1/tests/test_decode_chunks.py +112 -0
  130. spinf_serve-0.1.1/tests/test_extraction.py +318 -0
  131. spinf_serve-0.1.1/tests/test_fp8_checkpoint.py +47 -0
  132. spinf_serve-0.1.1/tests/test_manifests.py +95 -0
  133. spinf_serve-0.1.1/tests/test_media.py +204 -0
  134. spinf_serve-0.1.1/tests/test_pack_fixtures.py +83 -0
  135. spinf_serve-0.1.1/tests/test_packer.py +283 -0
  136. spinf_serve-0.1.1/tests/test_packs.py +783 -0
  137. spinf_serve-0.1.1/tests/test_protocol_compiler.py +396 -0
  138. spinf_serve-0.1.1/tests/test_public_hygiene.py +72 -0
  139. spinf_serve-0.1.1/tests/test_qwen_plan.py +190 -0
  140. spinf_serve-0.1.1/tests/test_qwen_vision.py +104 -0
  141. spinf_serve-0.1.1/tests/test_server_app.py +471 -0
  142. spinf_serve-0.1.1/tests/test_server_config.py +100 -0
  143. spinf_serve-0.1.1/tests/test_stacks.py +75 -0
  144. spinf_serve-0.1.1/tests/test_weights.py +129 -0
  145. spinf_serve-0.1.1/uv.lock +1423 -0
@@ -0,0 +1,29 @@
1
+ # The CPU suite and lint on every push and pull request. The GPU parity gate (README "Develop") runs by hand on a card: there is
2
+ # no GPU runner. The official packs' golden fixtures need a checkout of spinf-prompts next to the tests (SPINF_PROMPTS_DIR);
3
+ # without one those tests skip. SPINF_PROMPTS_REPO (a repository variable) and SPINF_PROMPTS_TOKEN (a secret) point a fork or a
4
+ # mirror at its own copy of the pack repository; unset, the public one is used.
5
+ name: ci
6
+ on:
7
+ push:
8
+ pull_request:
9
+ workflow_dispatch:
10
+ permissions:
11
+ contents: read
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+ - uses: actions/checkout@v7
18
+ id: prompts
19
+ continue-on-error: true
20
+ with:
21
+ repository: ${{ vars.SPINF_PROMPTS_REPO || 'spinfinc/spinf-prompts' }}
22
+ token: ${{ secrets.SPINF_PROMPTS_TOKEN || github.token }}
23
+ path: .spinf-prompts
24
+ - uses: astral-sh/setup-uv@v10.2.0
25
+ - run: uv sync --frozen --extra server
26
+ - run: uv run ruff check . && uv run ruff format --check .
27
+ - run: uv run pytest
28
+ env:
29
+ SPINF_PROMPTS_DIR: ${{ steps.prompts.outcome == 'success' && format('{0}/.spinf-prompts', github.workspace) || '' }}
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ .env
8
+ # the maintainers' news fixtures carry article text that is not cleared for publication: local only (the public fixture is
9
+ # fixtures/wikinews_*.json.gz)
10
+ fixtures/news_*
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,57 @@
1
+ # Acknowledgements
2
+
3
+ spinf-serve stands on other people's open work. This file names it: the models we run and re-quantize, the projects we
4
+ build on, the techniques we implement, and the data we checked our calibrations and prompts on. The legal attributions
5
+ for code included in this repository are in `NOTICE`; each checkpoint we publish credits its source in its model card
6
+ (`quantizations/*/MODEL_CARD.md`). The prompt packs have their own acknowledgements in the spinf-prompts repository.
7
+
8
+ Rule for contributors: the change that adds a dependency, vendors code, adopts a recipe, adds a model or checks results
9
+ on a dataset also adds the line here (and in `NOTICE` when code is included).
10
+
11
+ ## Models and checkpoints
12
+
13
+ | what | by | terms | how we use it |
14
+ |---|---|---|---|
15
+ | [Gemma 4](https://huggingface.co/google) 12B and 31B | Google DeepMind | Apache 2.0 (Gemma 4; Google's licence page: ai.google.dev/gemma/docs/gemma_4_license) | the `spinf/gemma-4-12b-fp8` / `spinf/gemma-4-31b-fp8` models: quantized copies with the original notices retained |
16
+ | [Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B) | Alibaba Qwen team | Apache 2.0 | the `spinf/qwen3.8-27b-int4` model (our GPTQ int4 copy) |
17
+ | [pplx-decider-v1-27b](https://huggingface.co/perplexity-ai/pplx-decider-v1-27b) | Perplexity AI | Apache 2.0 (derived from Qwen3.8-27B, revision 1d4bf0f2) | the decision model with a 255-code readout; our GPTQ int4 copy |
18
+ | [btbtyler09/Qwen3.8-27B-GPTQ-4bit](https://huggingface.co/btbtyler09/Qwen3.8-27B-GPTQ-4bit) | btbtyler09 | see the repo | the GPTQ recipe we adopted (GPTQModel 6.0.3, group 32, FOEM) and the checkpoint we served before publishing our own |
19
+
20
+ ## Projects
21
+
22
+ - [PyTorch](https://pytorch.org) (inductor, FlexAttention, `torch.compile`), [Triton](https://github.com/triton-lang/triton): the kernels.
23
+ - [Hugging Face](https://huggingface.co) transformers, tokenizers, safetensors, huggingface_hub: model definitions (the Gemma 4 and
24
+ Qwen3.5 modules, the vision towers), tokenization, weights.
25
+ - [flash-linear-attention](https://github.com/fla-org/flash-linear-attention) (Songlin Yang, Yu Zhang and contributors): the Gated
26
+ DeltaNet chunk kernels; one state kernel is vendored with changes (`NOTICE`).
27
+ - [GPTQModel](https://github.com/modelcloud/gptqmodel) (ModelCloud): the int4 quantization of our Qwen-architecture checkpoints.
28
+ - [vLLM](https://github.com/vllm-project/vllm): the reference for the fp8 scheme's numerics (`--quantization fp8`), the engine we
29
+ compare throughput against, and the OpenAI-compatible server shape we follow.
30
+ - [FastAPI](https://fastapi.tiangolo.com), [uvicorn](https://www.uvicorn.org), [pydantic](https://docs.pydantic.dev): the server and the protocol models.
31
+
32
+ ## Techniques
33
+
34
+ - GPTQ (Frantar, Ashkboos, Hoefler, Alistarh, 2022) and FOEM (First-Order Error Matters, AAAI 2026): the int4 weights.
35
+ - LLM.int8() (Dettmers, Lewis, Belkada, Zettlemoyer, 2022): the mixed-precision decomposition our i8 split scheme fixes in advance
36
+ (the outlier channels kept exact, the rest int8 per token).
37
+ - SmoothQuant (Xiao et al., 2022): evaluated as an alternative to the outlier split (docs/findings/QWEN.md).
38
+ - Gated DeltaNet (Yang, Kautz, Hatamizadeh, 2024): the recurrent layers of the Qwen3.5 architecture.
39
+ - FlexAttention (PyTorch): the tree masks that let many prompts share one read of the content.
40
+
41
+ ## Data
42
+
43
+ Used to calibrate the fp8 activation scales and the outlier channels and to check accuracy; never bundled, never used to
44
+ train anything that ships in this repository.
45
+
46
+ | dataset | used for | terms |
47
+ |---|---|---|
48
+ | [Wikinews](https://en.wikinews.org) articles | the public parity fixture and benchmark job set (`fixtures/`, 24 articles; each one's title, URL and revision in the fixture's `sources`) | CC BY 2.5 |
49
+ | Imagenette and Imagewoof (fast.ai) | image calibration and accuracy suites | Apache 2.0 |
50
+ | UCF101 | video suites | see the dataset's terms |
51
+ | ESC-50 | audio calibration and suite | CC BY-NC 3.0 (calibration and checks only) |
52
+ | GTZAN | audio suite | see the dataset's terms |
53
+ | RAGTruth | the outlier-channel calibration stream; a decision benchmark | see the dataset's terms |
54
+ | evol-codealpaca-v1 and C4 | GPTQ calibration samples (the adopted recipe) | see each dataset's terms (C4: ODC-BY) |
55
+ | the decision benchmarks of the pplx-decider card (Belebele, TruthfulQA, WinoGrande, Circa, FinancialPhraseBank, TabFact, RAGTruth, ContractNLI, JudgeBench, BBH, JevBench) | accuracy checks of the Qwen-architecture models | each set's own terms |
56
+
57
+ The news and moderation texts of our internal test bed are not redistributed; their sources are listed in that repository.
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 spinf Inc.
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,11 @@
1
+ spinf-serve
2
+ Copyright 2026 spinf Inc.
3
+
4
+ This product includes software developed by third parties:
5
+
6
+ - flash-linear-attention (fla-core 0.5.2), MIT License, Copyright (c) 2023-2026 Songlin Yang, Yu Zhang, Zhiyuan Li.
7
+ spinf_serve/models/qwen35/gdn.py vendors its Gated DeltaNet chunk state kernel (chunk_gated_delta_rule_fwd_kernel_h_blockdim64)
8
+ with changes (initial and final states addressed by row index).
9
+
10
+ The model checkpoints this engine runs are distributed by their authors under their own terms (see ACKNOWLEDGEMENTS.md);
11
+ none are included in this repository.
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.5
2
+ Name: spinf-serve
3
+ Version: 0.1.1
4
+ Summary: The open-source inference engine for decision models: one input read once, hundreds of questions scored in parallel on one GPU
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ License-File: NOTICE
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: pydantic<3,>=2.11
10
+ Provides-Extra: engine
11
+ Requires-Dist: flash-linear-attention==0.5.2; extra == 'engine'
12
+ Requires-Dist: huggingface-hub==1.30.0; extra == 'engine'
13
+ Requires-Dist: numpy==2.5.2; extra == 'engine'
14
+ Requires-Dist: pillow>=11; extra == 'engine'
15
+ Requires-Dist: safetensors==0.8.0; extra == 'engine'
16
+ Requires-Dist: tokenizers==0.23.2; extra == 'engine'
17
+ Requires-Dist: torch==2.14.0; extra == 'engine'
18
+ Requires-Dist: transformers==5.16.1; extra == 'engine'
19
+ Requires-Dist: triton==3.8.0; extra == 'engine'
20
+ Provides-Extra: server
21
+ Requires-Dist: fastapi>=0.118; extra == 'server'
22
+ Requires-Dist: flash-linear-attention==0.5.2; extra == 'server'
23
+ Requires-Dist: huggingface-hub==1.30.0; extra == 'server'
24
+ Requires-Dist: numpy==2.5.2; extra == 'server'
25
+ Requires-Dist: pillow>=11; extra == 'server'
26
+ Requires-Dist: safetensors==0.8.0; extra == 'server'
27
+ Requires-Dist: tokenizers==0.23.2; extra == 'server'
28
+ Requires-Dist: torch==2.14.0; extra == 'server'
29
+ Requires-Dist: transformers==5.16.1; extra == 'server'
30
+ Requires-Dist: triton==3.8.0; extra == 'server'
31
+ Requires-Dist: uvicorn>=0.37; extra == 'server'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # spinf-serve
35
+
36
+ **The open-source inference engine for decision models.**
37
+
38
+ spinf-serve reads a piece of content once and answers hundreds of questions about it in parallel, as probabilities
39
+ rather than text. It turns a standard open model (Gemma 4, Qwen3.8) into a decision model through **decision mode**:
40
+ one input, many questions, scores instead of generation, and the same score whatever else is in the batch. It runs on
41
+ one 24 GB GPU and serves an OpenAI-compatible API that is also the API of the hosted spinf service.
42
+
43
+ ## What it does
44
+
45
+ - **Scoring.** A question is a template that ends where the answer goes, with the answers to score there. The engine
46
+ returns the probability of each answer, its log-probability, and the same question's probability on empty content
47
+ (the floor), so a score can be read as a lift over the question's own bias.
48
+ - **Many questions, one read.** The content is read once; every question is a branch of the same pass. Adding a
49
+ question costs its own tokens only. Placeholders turn one template into a grid of questions.
50
+ - **Extraction and generation** from the same read: `{*}` text, `{n}` numbers and `{$}` amounts decoded with their
51
+ probabilities, or free text generated into a slot.
52
+ - **Batch invariance.** With the calibrated fp8 scheme, an input's scores are bit-identical whether it is scored
53
+ alone or packed with thousands of others. Scores compare across calls, days and servers; a `system_fingerprint`
54
+ says when they do not.
55
+ - **Media.** Images, audio and video frames in the content, read by the models that support them.
56
+ - **Prompt packs.** Ready-made, calibrated question sets preloaded into the server and named in a request
57
+ ([below](#prompt-packs)).
58
+ - **Throughput.** Inputs are packed into 16k-token passes on one GPU: about 10,000 tokens per second on an RTX 4090
59
+ with the 12B model.
60
+ - **One API, hosted or self-hosted.** The request and response are those of `api.spinf.com`; a client switches by
61
+ changing the base URL and the key.
62
+
63
+ ## Speed and scale
64
+
65
+ spinf-serve exists for one workload: **many questions about one content**. Our own first use, a daily oil-market
66
+ index read from 15 million news articles, taught us that asking thousands of narrow, calibrated questions of every
67
+ article gives a far more accurate read than any single prompt, and that no engine built for chat could do it at a
68
+ price that made sense. A chat engine re-reads the content for every question; even with prefix caching it generates
69
+ one token at a time per prompt and treats every question as a separate request. spinf-serve reads the content once
70
+ and lays every question out as a branch of that read, packed with other inputs into 16k-token passes on one GPU.
71
+
72
+ Measured on one RTX 4090 with Gemma 4 12B in fp8, the same 1,344 questions (24 news articles of about 470 tokens, 56
73
+ questions each, every answer a probability over two options: the public job set in `fixtures/`) sent over HTTP to
74
+ spinf-serve and to **vLLM 0.26 with prefix caching**, which is what most people would reach for (`benchmarks/`):
75
+
76
+ | one RTX 4090, warm, per second | spinf-serve | vLLM 0.26, prefix caching, 128 requests in flight |
77
+ |---|---|---|
78
+ | questions answered | **491** | 200 |
79
+ | tokens of work processed (content once, then each question's own tokens) | 11,200 | 4,600 |
80
+ | the same result for an engine that re-reads the content per question | 236,000 tokens | 96,000 tokens |
81
+ | the 1,344 questions | **2.7 s** | 6.7 s |
82
+
83
+ 2.5 times faster on the same card for the same answers, and vLLM is already doing the right thing here: its prefix
84
+ cache reuses each article across its 56 questions. What it cannot do is answer the questions in one forward pass, so it
85
+ runs 1,344 single-token requests against the cache, while spinf-serve lays the 56 questions out as branches of one read
86
+ and packs the 24 articles into two 16k-token passes. The gap grows with the number of questions per content: at 56
87
+ questions the shared read saves 21 times the tokens; at a thousand it saves hundreds of times, and a thousand questions
88
+ on one content still cost about what the questions themselves weigh. Processed-token throughput stays at the engine's
89
+ rate, about 11,000 per second per 4090 for the 12B model (on an earlier set of 18 shorter articles with 74 questions each
90
+ the ratio was 2.9). The int4 Qwen3.8 27B model showed the same ratio range (2.2 to 2.8 times vLLM with prefix caching) on
91
+ 2,211 decision-benchmark jobs in our internal measurements. Anyone can rerun the comparison: `benchmarks/README.md`.
92
+
93
+ What this means for a job:
94
+
95
+ - **Content moderation**: the `spinf/moderation` pack asks 106 questions of every message (22 categories, three
96
+ decision modes). One 4090 moderates about 20,000 messages per hour through the full pack, each message scored for
97
+ every category in one read.
98
+ - **News and document analysis**: 74 calibrated questions per article, aggregated into indexes, at nearly 700
99
+ questions per second per GPU.
100
+ - **Triage, routing, tagging**: intent in two levels, urgency, sentiment, risk flags, personal-data checks and a
101
+ policy check in one pass, each a question in the same list, each costing its own tokens only.
102
+
103
+ **Extraction and generation** ride the same read but add decoding steps: a searched slot (`{*}` `{n}` `{$}`) costs
104
+ about as much GPU time as 200 to 600 tokens of scoring, a generated slot up to about 2,000. They still benefit from
105
+ the shared read and the packing: the content and the questions are not re-read, the slots of many inputs are decoded
106
+ together, and a few slots next to hundreds of scored questions change the pass's cost only a little.
107
+
108
+ The example packs (triage, product listings, entertainment metadata, reviews, brand analysis) are in
109
+ [spinf-prompts](https://github.com/spinfinc/spinf-prompts).
110
+
111
+ ## Prompt packs
112
+
113
+ A specialised use case is rarely one question: content moderation is 106 of them, each calibrated against normal
114
+ traffic and combined into category scores and a decision. spinf-serve standardises a format for this: a **prompt pack**
115
+ is one file holding a set of questions, how to present the content to the model, how the answers turn into scores,
116
+ and how the scores turn into a decision. Packs are preloaded into the server, so a request names one (`"packs": [{"id":
117
+ "spinf/moderation"}]`) instead of carrying hundreds of questions over and over, and gets the pack's categories and
118
+ decision back next to its own questions, from the same read of the content.
119
+
120
+ Because a pack is a file, it is also a **ready-to-go, shareable set**: the server loads packs from
121
+ [spinf-prompts](https://github.com/spinfinc/spinf-prompts), the official repository (content moderation first), and
122
+ from any GitHub repository or local folder you point it at, enabling the ones written for the model it serves. Publish
123
+ your own with a manifest at the root of your repository. Details, the file format and the request fields:
124
+ [docs/prompt-packs.md](docs/prompt-packs.md).
125
+
126
+ ## Install and run
127
+
128
+ Linux, an NVIDIA GPU with 24 GB (fp8 tensor cores for the batch-invariant scheme: RTX 4090 / 5090, L4, L40S, RTX 6000
129
+ Ada, H100), a driver supporting CUDA 13, Python 3.12.
130
+
131
+ ```sh
132
+ pip install "spinf-serve[server]"
133
+ spinf-serve serve spinf/gemma-4-12b-fp8 # downloads the checkpoint on first start; serves on port 8000
134
+ ```
135
+
136
+ Then ask. Two classifications and one extraction, answered from a single read of the content:
137
+
138
+ ```sh
139
+ curl http://localhost:8000/v1/score \
140
+ -H "Content-Type: application/json" \
141
+ -d '{
142
+ "model": "spinf/gemma-4-12b-fp8",
143
+ "messages": [
144
+ { "role": "user",
145
+ "content": "Customer support message:\nI was charged twice for September, please refund one of the charges." }
146
+ ],
147
+ "scoring": {
148
+ "queries": [
149
+ { "id": "topic",
150
+ "template": "\n\nTopic (billing, technical, account, shipping, or cancellation):{?}",
151
+ "options": [" billing", " technical", " account", " shipping", " cancellation"] },
152
+ { "id": "refund",
153
+ "template": "\n\nIs the customer asking for a refund?\nAnswer:{?}",
154
+ "options": [" yes", " no"] },
155
+ { "id": "month",
156
+ "template": "\n\nQ: Which month was charged twice?\nA: \"{*}\"" }
157
+ ]
158
+ }
159
+ }'
160
+ ```
161
+
162
+ The response gives, for `topic` and `refund`, the probability of each answer and the same probability on empty
163
+ content (the lift between the two is what the message says), and for `month` the most likely answers with their
164
+ probabilities. The same body, with an API key, runs against the hosted API at `api.spinf.com`; the OpenAI SDKs work
165
+ through `/v1/chat/completions` with the `scoring` block in `extra_body` ([quickstart](docs/quickstart.md)).
166
+
167
+ As a library: `from spinf_serve import Engine; Engine("spinf/gemma-4-12b-fp8").score(request)`.
168
+
169
+ ## Models
170
+
171
+ | name | based on | GPU | reads |
172
+ |---|---|---|---|
173
+ | `spinf/gemma-4-12b-fp8` (default) | Google Gemma 4 12B, FP8 with static activation scales | one 24 GB | text, images, audio, video |
174
+ | `spinf/gemma-4-31b-fp8` | Google Gemma 4 31B, FP8 static | one 48 GB | text, images, video |
175
+ | `spinf/qwen3.8-27b-int4` | Qwen3.8-27B, GPTQ int4 | one 24 GB | text, images, video |
176
+ | `spinf/pplx-decider-v1-27b-int4` | Perplexity pplx-decider-v1-27b, GPTQ int4: a decision model with a 255-code readout | one 24 GB | text; scores only, with its own prompts and packs |
177
+
178
+ Each is a checkpoint we quantized and calibrated, published on the Hugging Face Hub with its recipe
179
+ (`quantizations/`); the engine pins the revision it was parity-tested on. Any other Hub checkpoint of a supported
180
+ architecture family runs by its path, quantized at load time.
181
+
182
+ ## Documentation
183
+
184
+ [docs/](docs/README.md): [quickstart](docs/quickstart.md), [examples](docs/examples.md) (curl calls for one question,
185
+ parallel classifications, classification plus extraction, many contents, a prompt pack),
186
+ [scoring API](docs/scoring-api.md), [configuration](docs/configuration.md), [writing questions](docs/writing-questions.md),
187
+ [calibration](docs/calibration.md), [prompt packs](docs/prompt-packs.md), [FAQ](docs/faq.md); the [guides](docs/guides/index.md)
188
+ (decision models, probabilities you can act on, few-shot classification, sentiment, survey coding, moderation, intent routing)
189
+ and the engineering documents ([docs/README.md](docs/README.md#engineering-documents)).
190
+
191
+ ## Status
192
+
193
+ The engine (scoring, extraction, generation, media, batch invariance, the two model stacks) is complete and
194
+ parity-tested against recorded references. The server speaks the public API of the hosted service: `spinf-serve serve`,
195
+ `/v1/score` and its chat-completions alias, `/v1/models`, `/v1/packs`, pack sources, the response and error envelopes.
196
+ The `spinf/...` checkpoints are being published; [docs/README.md](docs/README.md#status) keeps the list of what is
197
+ left. How the engine works and the measurements behind it: the engineering documents in
198
+ [docs/](docs/README.md#engineering-documents) (architecture, batch invariance, precision, the Qwen stack, extraction).
199
+
200
+ ## Layout
201
+
202
+ ```
203
+ spinf_serve/
204
+ protocol.py, compiler.py, packer.py, scorer.py, decode.py, extract.py, media.py, service.py
205
+ the engine: request shape, templates to token tries, packing, one packed forward, extraction
206
+ manifests/ the served models (TOML): pinned checkpoint, family, schemes, capabilities, media, limits
207
+ packs/ prompt packs (pure Python): the file and its validation, the evaluator, a request's packs, the sources
208
+ (GitHub, folders, the official repository) and the catalog; `python -m spinf_serve.packs validate | list`
209
+ models/ one stack per architecture family behind one interface (base.py, registry.py): gemma4.py, qwen35/
210
+ quant/ the quantization schemes (linears, kernels, the fp8-static layout), the per-revision tables, the maintainer
211
+ commands that produce our checkpoints
212
+ server/ the HTTP server (FastAPI, one process per GPU): the public API around the engine (api.py), the settings
213
+ (config.py: flags > environment > config file > manifest), the app
214
+ cli.py the `spinf-serve` command: serve, score, packs, parity, calibrate
215
+ quantizations/ one folder per published checkpoint: recipe, calibration output, model card
216
+ fixtures/ the public parity fixture (24 Wikinews articles x 56 cells) and one recorded reference per checkpoint
217
+ benchmarks/ the throughput comparison script (spinf-serve against a completions server), its results, the fixture builder
218
+ tests/ CPU tests (run in CI); the GPU parity gate runs by hand (below)
219
+ docs/ the user guides and the engineering documents
220
+ ```
221
+
222
+ ## Develop
223
+
224
+ ```sh
225
+ uv sync --extra server
226
+ uv run pytest # CPU tests (the official packs' golden fixtures need ../spinf-prompts or SPINF_PROMPTS_DIR)
227
+ uv run ruff check . && uv run ruff format --check .
228
+ CUDA_VISIBLE_DEVICES=0 uv run spinf-serve parity --fixture fixtures/wikinews_20261024.json.gz \
229
+ --reference fixtures/wikinews_20261024.spinf-gemma-4-12b-fp8.json.gz --passes 2
230
+ ```
231
+
232
+ Every change that touches a kernel, a compile boundary or a quantization scheme ships with the GPU parity run:
233
+ bit-identical log-probabilities on the recorded reference of each published checkpoint ([fixtures/](fixtures/README.md):
234
+ 24 Wikinews articles x 56 two-option cells, a reference per model recorded and verified on its card). Scores come from
235
+ exact dependency pins; they change only together with a parity run.
236
+
237
+ ## Licence and acknowledgements
238
+
239
+ Apache 2.0 (`LICENSE`, `NOTICE`). The models we run and re-quantize, the projects we build on, the techniques we
240
+ implement and the data we checked on are credited in [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md).