tinydistill 0.3.0__tar.gz → 0.3.2__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 (80) hide show
  1. {tinydistill-0.3.0 → tinydistill-0.3.2}/.gitignore +1 -0
  2. {tinydistill-0.3.0 → tinydistill-0.3.2}/PKG-INFO +180 -189
  3. {tinydistill-0.3.0 → tinydistill-0.3.2}/README.md +172 -187
  4. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/run.sh +12 -1
  5. tinydistill-0.3.2/examples/banking77/students/all-MiniLM-L12-v2-20-epochs.json +146 -0
  6. tinydistill-0.3.2/examples/banking77/students/all-MiniLM-L12-v2.json +142 -0
  7. tinydistill-0.3.2/examples/banking77/students/all-MiniLM-L6-v2-10-epochs.json +146 -0
  8. tinydistill-0.3.2/examples/banking77/students/all-MiniLM-L6-v2-20-epochs.json +146 -0
  9. tinydistill-0.3.2/examples/banking77/students/all-mpnet-base-v2.json +142 -0
  10. tinydistill-0.3.2/examples/banking77/students/bge-small-en-v1.5.json +142 -0
  11. tinydistill-0.3.2/examples/banking77/students/static.json +146 -0
  12. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/students.md +3 -0
  13. tinydistill-0.3.2/examples/financial-tweets/financial-tweets-7b-20-epochs.json +146 -0
  14. tinydistill-0.3.2/examples/financial-tweets/financial-tweets-7b-20-epochs.md +22 -0
  15. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/run.sh +6 -3
  16. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/go-emotions/run.sh +1 -1
  17. tinydistill-0.3.2/examples/headline.md +5 -0
  18. tinydistill-0.3.2/examples/update_readme.py +66 -0
  19. {tinydistill-0.3.0 → tinydistill-0.3.2}/pyproject.toml +8 -2
  20. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/__init__.py +1 -1
  21. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/analyse.py +3 -1
  22. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/cli.py +105 -35
  23. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/evaluate.py +8 -0
  24. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/export.py +102 -3
  25. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/hub.py +11 -5
  26. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/progress.py +25 -9
  27. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/static.py +3 -1
  28. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/student.py +56 -30
  29. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/teacher.py +28 -0
  30. tinydistill-0.3.2/src/tinydistill/web.py +537 -0
  31. tinydistill-0.3.2/tests/browser/build_tiny_site.py +49 -0
  32. tinydistill-0.3.2/tests/browser/check_page.mjs +44 -0
  33. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_cli.py +158 -38
  34. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_export.py +27 -0
  35. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_loss.py +29 -0
  36. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_progress.py +5 -3
  37. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_serve.py +23 -11
  38. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_task.py +1 -1
  39. tinydistill-0.3.0/examples/update_readme.py +0 -20
  40. tinydistill-0.3.0/src/tinydistill/web.py +0 -171
  41. {tinydistill-0.3.0 → tinydistill-0.3.2}/LICENSE +0 -0
  42. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-1.5b-8shot.json +0 -0
  43. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-1.5b-8shot.md +0 -0
  44. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-1.5b.json +0 -0
  45. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-1.5b.md +0 -0
  46. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-7b-8shot.json +0 -0
  47. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-7b-8shot.md +0 -0
  48. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-7b.json +0 -0
  49. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/banking77-7b.md +0 -0
  50. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/task.yaml +0 -0
  51. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/banking77/teachers.md +0 -0
  52. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-1.5b-8shot.json +0 -0
  53. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-1.5b-8shot.md +0 -0
  54. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-1.5b.json +0 -0
  55. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-1.5b.md +0 -0
  56. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-7b.json +0 -0
  57. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-7b.md +0 -0
  58. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-ensemble.json +0 -0
  59. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-ensemble.md +0 -0
  60. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-synthetic.json +0 -0
  61. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/financial-tweets-synthetic.md +0 -0
  62. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/task.yaml +0 -0
  63. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/financial-tweets/teachers.md +0 -0
  64. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/go-emotions/go-emotions-1.5b.json +0 -0
  65. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/go-emotions/go-emotions-1.5b.md +0 -0
  66. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/go-emotions/task.yaml +0 -0
  67. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/vllm-smoke.json +0 -0
  68. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/vllm-smoke.md +0 -0
  69. {tinydistill-0.3.0 → tinydistill-0.3.2}/examples/vllm-smoke.sh +0 -0
  70. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/data.py +0 -0
  71. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/serve.py +0 -0
  72. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/shortlist.py +0 -0
  73. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/suggest.py +0 -0
  74. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/synth.py +0 -0
  75. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/task.py +0 -0
  76. {tinydistill-0.3.0 → tinydistill-0.3.2}/src/tinydistill/tune.py +0 -0
  77. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/conftest.py +0 -0
  78. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_cache.py +0 -0
  79. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_scoring.py +0 -0
  80. {tinydistill-0.3.0 → tinydistill-0.3.2}/tests/test_vllm.py +0 -0
@@ -7,3 +7,4 @@ __pycache__/
7
7
  .pytest_cache/
8
8
  .ruff_cache/
9
9
  .venv-vllm/
10
+ node_modules/
@@ -1,20 +1,26 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tinydistill
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Distil an LLM classification prompt into a small, fast model that runs on a CPU.
5
5
  Project-URL: Homepage, https://github.com/Andreas-Hum/tinydistill
6
6
  Author: Andreas-Hum
7
7
  License-Expression: MIT
8
8
  License-File: LICENSE
9
9
  Keywords: inference,knowledge distillation,llm,onnx,text classification
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
10
12
  Classifier: Operating System :: OS Independent
11
13
  Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
12
17
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
18
  Requires-Python: >=3.11
19
+ Requires-Dist: numpy>=1.24
14
20
  Requires-Dist: onnx>=1.15
15
21
  Requires-Dist: onnxruntime>=1.17
16
22
  Requires-Dist: pyyaml>=6
17
- Requires-Dist: torch>=2.2
23
+ Requires-Dist: torch>=2.6
18
24
  Requires-Dist: transformers>=4.56
19
25
  Provides-Extra: 4bit
20
26
  Requires-Dist: accelerate>=0.30; extra == '4bit'
@@ -30,200 +36,83 @@ Description-Content-Type: text/markdown
30
36
 
31
37
  # tinydistill
32
38
 
33
- **Most of a large model's quality on one task, many times faster, on a CPU.**
39
+ <a href="https://pypi.org/project/tinydistill/"><img src="https://img.shields.io/pypi/v/tinydistill.svg" alt="PyPI version"></a>
40
+ <a href="https://pypi.org/project/tinydistill/"><img src="https://img.shields.io/pypi/pyversions/tinydistill.svg" alt="Supported Python versions"></a>
41
+ <a href="https://github.com/Andreas-Hum/tinydistill/actions/workflows/ci.yml"><img src="https://github.com/Andreas-Hum/tinydistill/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
42
+ <a href="https://pepy.tech/projects/tinydistill"><img src="https://static.pepy.tech/badge/tinydistill/month" alt="Downloads per month"></a>
43
+ <a href="https://github.com/Andreas-Hum/tinydistill/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Andreas-Hum/tinydistill.svg" alt="MIT licence"></a>
44
+ <a href="https://colab.research.google.com/github/Andreas-Hum/tinydistill/blob/main/notebooks/quickstart.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open in Colab"></a>
45
+ <a href="https://andreas-hum.github.io/tinydistill/"><img src="https://img.shields.io/badge/demo-live-2a78d6.svg" alt="Live demo"></a>
34
46
 
35
- `tinydistill` turns an LLM classification prompt into a small encoder classifier by knowledge distillation.
36
- You describe the task in a short YAML file (labels, how to say each label, an instruction prompt) and give it
37
- unlabelled texts. An instruction-tuned LLM labels them with soft targets, a small encoder learns to imitate
38
- those targets, and the result is exported to ONNX (optionally int8) for fast CPU inference.
47
+ **Most of a large model's quality on one task, many times faster, on a CPU.**
39
48
 
40
- It continues my bachelor's project on inference optimisation by distilling LLMs.
49
+ `tinydistill` turns an LLM classification prompt into a small classifier by knowledge distillation. Describe
50
+ the task in a short YAML file and give it unlabelled texts: an instruction-tuned LLM labels them with soft
51
+ targets, a small encoder learns to imitate them, and the result is exported to ONNX (optionally int8) for fast
52
+ inference on a CPU, in a browser or behind an HTTP server. It continues my bachelor's project on inference
53
+ optimisation by distilling LLMs.
54
+
55
+ <p align="center">
56
+ <a href="https://andreas-hum.github.io/tinydistill/">
57
+ <picture>
58
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Andreas-Hum/tinydistill/main/docs/images/demo-dark.png">
59
+ <img alt="The live demo: a 23 MB Banking77 classifier running in the browser, showing the word pieces and what each layer of the model would answer" src="https://raw.githubusercontent.com/Andreas-Hum/tinydistill/main/docs/images/demo-light.png" width="640">
60
+ </picture>
61
+ </a>
62
+ </p>
63
+ <p align="center"><i>The <a href="https://andreas-hum.github.io/tinydistill/">live demo</a>: a 23 MB student distilled from Qwen2.5-7B runs in your
64
+ browser and shows what happens inside it.</i></p>
65
+
66
+ ## Highlights
67
+
68
+ - **No labels needed.** Any instruction-tuned LLM on the Hugging Face Hub labels your texts by scoring every
69
+ label in one forward pass: no text generation, no answer parsing ([how](#how-the-label-scoring-works)).
70
+ - **Small, fast students.** A 23 MB MiniLM or an 8 MB static-embedding model, more accurate than its 7B teacher
71
+ on Banking77 and over 200x faster on a CPU than the teacher on a GPU ([results](#results)).
72
+ - **Any teacher.** Local models in full precision or 4 bit, ensembles of several model families, or a remote
73
+ [vLLM](https://github.com/vllm-project/vllm) server ([choosing the teacher](#choosing-the-teacher)).
74
+ - **Honest numbers.** Every run reports the accuracy, latency and size of teacher and student, with gold-only
75
+ baselines, upper bounds and several seeds.
76
+ - **A few labels go a long way.** Gold examples can go into the teacher's prompt and the student's loss
77
+ ([a few gold labels](#a-few-gold-labels)).
78
+ - **Tools around the loop.** Tune the setup on a dev split, see where it goes wrong, pick texts to label,
79
+ route unsure texts to the LLM and retrain on them ([improving a run](#improving-a-run)).
80
+ - **Deploy anywhere.** ONNX and int8, `tinydistill serve`, a static web page, or the Hugging Face Hub and
81
+ `Predictor("hf:user/name")` ([deploying](#deploying-cascade-server-browser)).
82
+
83
+ ## Installation
41
84
 
42
- ```mermaid
43
- flowchart LR
44
- A["Task prompt +<br/>unlabelled texts"] --> B["LLM teacher(s)<br/>Qwen · Mistral · Phi · ..."]
45
- S["Synthetic texts<br/>(synth)"] -.-> B
46
- V["vLLM server"] -.-> B
47
- K["A few gold labels<br/>per class"] -.-> B
48
- K -.-> C
49
- B -- "soft labels<br/>(ensembled)" --> C["Small encoder<br/>student"]
50
- C --> D["ONNX / int8<br/>export"]
51
- D --> E["Fast CPU<br/>inference"]
52
- D --> W["Browser · server ·<br/>Hugging Face Hub"]
53
- E -.-> F["Cascade: unsure<br/>texts to the LLM"]
85
+ ```sh
86
+ pip install tinydistill # label, train, export, predict
87
+ pip install "tinydistill[hf]" # + Hugging Face datasets as input (hf:<dataset>:<split>)
88
+ pip install "tinydistill[hf,4bit]" # + 4-bit teachers with bitsandbytes (CUDA)
54
89
  ```
55
90
 
56
- Each step is one command: `tinydistill label` (teacher), `train` (student), `export` (ONNX / int8) and
57
- `predict` (inference), or `tinydistill run` for all of them. Dashed parts are optional: synthetic texts when
58
- there is no unlabelled data, a vLLM server as the teacher, a few gold labels for the teacher's prompt and the
59
- student's loss, and a cascade that hands the student's least confident texts to the LLM. The exported model
60
- runs on a CPU, in a [browser](#deploying-cascade-server-browser), behind `tinydistill serve`, or from the
61
- Hugging Face Hub.
62
-
63
- ## Results
64
-
65
- Teachers are `Qwen/Qwen2.5-1.5B-Instruct` and `Qwen/Qwen2.5-7B-Instruct` (4 bit), zero-shot or with 8 gold
66
- examples per class in the prompt; the student is `sentence-transformers/all-MiniLM-L6-v2` unless a table says
67
- otherwise. Quality is measured on gold-labelled test texts that no model trains on, and students are trained
68
- with 3 seeds (mean ± standard deviation). Every table below is filled by the task's `run.sh` from its saved
69
- results files in [`examples/`](https://github.com/Andreas-Hum/tinydistill/tree/main/examples); the text only
70
- quotes them.
71
-
72
- **Try it:** the [live demo](https://andreas-hum.github.io/tinydistill/) runs the Banking77 student (7B 8-shot
73
- teacher, int8, 23 MB) in your browser.
74
-
75
- ### Banking77: the hard case
76
-
77
- Intent detection with 77 fine-grained classes ([Banking77](https://huggingface.co/datasets/PolyAI/banking77),
78
- CC BY 4.0), reproduced by [`examples/banking77/run.sh`](https://github.com/Andreas-Hum/tinydistill/blob/main/examples/banking77/run.sh).
79
- One row per teacher; the student columns are the deployed int8 model:
80
-
81
- <!-- results: banking77/teachers.md -->
82
- | Run | Teacher | Teacher accuracy | Student | Parameters (M) | Accuracy | Macro-F1 | Agreement with teacher | Deployed as | Latency, batch 1 (ms) | Latency, batch 32 (ms) | Size (MB) |
83
- |---|---|---|---|---|---|---|---|---|---|---|---|
84
- | banking77-1.5b | zero-shot `Qwen/Qwen2.5-1.5B-Instruct` | 0.404 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.386 ± 0.004 | 0.313 ± 0.005 | 0.766 ± 0.004 | ONNX int8 | 1.2 | 37.3 | 23 |
85
- | banking77-1.5b-8shot | 8-shot `Qwen/Qwen2.5-1.5B-Instruct` | 0.513 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.485 ± 0.009 | 0.406 ± 0.007 | 0.706 ± 0.002 | ONNX int8 | 1.1 | 36.4 | 23 |
86
- | banking77-7b | zero-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.629 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.618 ± 0.003 | 0.579 ± 0.005 | 0.817 ± 0.007 | ONNX int8 | 1.4 | 39.2 | 23 |
87
- | banking77-7b-8shot | 8-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.783 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.729 ± 0.011 | 0.697 ± 0.015 | 0.800 ± 0.003 | ONNX int8 | 1.4 | 40.2 | 23 |
88
-
89
- All latencies on the CPU with 2 threads.
90
- <!-- /results -->
91
-
92
- The student follows its teacher, so the teacher sets the ceiling: a bigger model and 8 examples per class
93
- each help a lot. The full table for the best teacher, with the student trained on the same 616 gold labels
94
- alone (baseline) and on all 10,003 gold labels (upper bound), and the cascade:
95
-
96
- <!-- results: banking77/banking77-7b-8shot.md -->
97
- | Model | Device | Accuracy | Macro-F1 | Agreement with teacher | Latency, batch 1 (ms) | Latency, batch 32 (ms) | Throughput (texts/s) | Size (MB) | Speed-up, batch 1 |
98
- |---|---|---|---|---|---|---|---|---|---|
99
- | teacher, 8-shot, 4-bit | cuda | 0.783 | 0.779 | n/a | 264.0 | 7434.4 | 4.3 | 5443 | 1.0x |
100
- | student, PyTorch | cpu | 0.729 ± 0.011 | 0.697 ± 0.015 | 0.800 ± 0.003 | 4.4 | 89.3 | 358.5 | 91 | 60.3x |
101
- | student, ONNX fp32 | cpu | 0.727 | 0.696 | 0.801 | 3.4 | 90.9 | 352.2 | 91 | 77.6x |
102
- | student, ONNX int8 | cpu | 0.726 | 0.694 | 0.797 | 1.4 | 40.2 | 795.7 | 23 | 186.3x |
103
- | baseline: student on 8 gold labels per class, no teacher | cpu | 0.490 ± 0.014 | 0.449 ± 0.018 | 0.473 ± 0.015 | n/a | n/a | n/a | n/a | n/a |
104
- | upper bound: student on all gold labels | cpu | 0.914 ± 0.005 | 0.914 ± 0.005 | 0.775 ± 0.001 | n/a | n/a | n/a | n/a | n/a |
105
-
106
- Task `banking77`, 3080 gold-labelled test texts. Teacher: `Qwen/Qwen2.5-7B-Instruct`. Student: `sentence-transformers/all-MiniLM-L6-v2`. The teacher's prompt shows 8 gold training examples per class (616 texts). The student learns from the teacher's soft labels on all 10003 training texts, plus cross-entropy on 8 gold labels per class (616 texts, weight 1.0). The baseline is the same student trained with cross-entropy on those 616 gold labels only, without the teacher. The upper bound is the same student trained with cross-entropy on all 10003 gold training labels and no teacher; its speed equals the student's. Values with ± are the mean and standard deviation over 3 training seeds; the ONNX rows are the exported first-seed model.
107
- CPU rows use 2 threads. Latency is the median over repeated runs. Size is the teacher's weights as loaded and the students' model files. The teacher packs several texts into one forward pass; it is timed at batch 32 only when that takes under three minutes, otherwise its throughput is 1 / batch-1 latency. Speed-up is relative to the teacher, 8-shot, 4-bit on cuda.
108
-
109
- ### Cascade: student first, unsure texts to the teacher
110
-
111
- | Share sent to the teacher | Student confidence threshold | Accuracy | Macro-F1 | Mean latency per text (ms) |
112
- |---|---|---|---|---|
113
- | 0% | 0.080 | 0.726 | 0.694 | 1.4 |
114
- | 10% | 0.231 | 0.744 | 0.718 | 27.8 |
115
- | 20% | 0.302 | 0.750 | 0.730 | 54.2 |
116
- | 30% | 0.355 | 0.764 | 0.748 | 80.6 |
117
- | 50% | 0.440 | 0.778 | 0.769 | 133.4 |
118
- | 100% | 1.000 | 0.783 | 0.779 | 265.4 |
119
-
120
- The student answers every text and passes its least confident ones to the teacher. Thresholds are set on the test texts to hit each share; in deployment, set them on a validation split. Latency is the student's batch-1 latency plus the share times the batch-1 latency of the teacher, 8-shot, 4-bit on cuda.
121
- <!-- /results -->
122
-
123
- The distilled student is far better than the same student trained on the few gold labels alone, and runs on a
124
- CPU in about a millisecond. Sending its least confident texts to the teacher closes most of the remaining gap
125
- to the teacher.
126
-
127
- Other students, the same teacher and soft labels. `static` is the static-embedding student
128
- (`--student static`: token vectors from MiniLM-L6, averaged, no attention), trained for 20 epochs at learning
129
- rate 1e-3 instead of 5 epochs at 1e-4:
130
-
131
- <!-- results: banking77/students.md -->
132
- | Run | Teacher | Teacher accuracy | Student | Parameters (M) | Accuracy | Macro-F1 | Agreement with teacher | Deployed as | Latency, batch 1 (ms) | Latency, batch 32 (ms) | Size (MB) |
133
- |---|---|---|---|---|---|---|---|---|---|---|---|
134
- | banking77-7b-8shot | 8-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.783 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.729 ± 0.011 | 0.697 ± 0.015 | 0.800 ± 0.003 | ONNX int8 | 1.4 | 40.2 | 23 |
135
- | all-MiniLM-L12-v2 | 8-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.783 | `sentence-transformers/all-MiniLM-L12-v2` | 33 | 0.743 ± 0.011 | 0.710 ± 0.016 | 0.815 ± 0.004 | ONNX int8 | 3.3 | 79.2 | 34 |
136
- | all-mpnet-base-v2 | 8-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.783 | `sentence-transformers/all-mpnet-base-v2` | 110 | 0.814 ± 0.004 | 0.805 ± 0.007 | 0.863 ± 0.001 | ONNX int8 | 7.3 | 220.4 | 110 |
137
- | bge-small-en-v1.5 | 8-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.783 | `BAAI/bge-small-en-v1.5` | 33 | 0.739 ± 0.008 | 0.706 ± 0.011 | 0.815 ± 0.004 | ONNX int8 | 2.7 | 75.2 | 34 |
138
- | static | 8-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.783 | `static` | 8 | 0.803 ± 0.001 | 0.801 ± 0.001 | 0.824 ± 0.001 | ONNX int8 | 0.1 | 5.1 | 8 |
139
-
140
- All latencies on the CPU with 2 threads.
141
- <!-- /results -->
142
-
143
- Two students are more accurate than their own teacher: the 110M-parameter mpnet and the static student.
144
- Learning from the teacher's soft labels on 10,000 texts, plus the 616 gold labels, smooths out some of its
145
- individual mistakes. The static student does this at about a tenth of a millisecond per text and 8 MB. It
146
- also trained four times longer, so the gap to the small transformers is not only about architecture.
147
-
148
- ### Financial tweets: an easier task
149
-
150
- Sentiment of financial news tweets, 3 classes
151
- ([Twitter Financial News Sentiment](https://huggingface.co/datasets/zeroshot/twitter-financial-news-sentiment),
152
- MIT licence per the dataset card), reproduced by
153
- [`examples/financial-tweets/run.sh`](https://github.com/Andreas-Hum/tinydistill/blob/main/examples/financial-tweets/run.sh).
154
- The validation split is the test set. The rows include an ensemble of three model families (Qwen2.5-7B,
155
- Mistral-7B and Phi-3.5-mini, all 4 bit) and a student trained only on texts the teacher wrote itself
156
- (`tinydistill synth`, no real training texts):
157
-
158
- <!-- results: financial-tweets/teachers.md -->
159
- | Run | Teacher | Teacher accuracy | Student | Parameters (M) | Accuracy | Macro-F1 | Agreement with teacher | Deployed as | Latency, batch 1 (ms) | Latency, batch 32 (ms) | Size (MB) |
160
- |---|---|---|---|---|---|---|---|---|---|---|---|
161
- | financial-tweets-1.5b | zero-shot `Qwen/Qwen2.5-1.5B-Instruct` | 0.537 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.514 ± 0.004 | 0.538 ± 0.004 | 0.810 ± 0.004 | ONNX int8 | 1.6 | 85.1 | 23 |
162
- | financial-tweets-1.5b-8shot | 8-shot `Qwen/Qwen2.5-1.5B-Instruct` | 0.314 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.295 ± 0.003 | 0.276 ± 0.003 | 0.870 ± 0.006 | ONNX int8 | 1.8 | 77.2 | 23 |
163
- | financial-tweets-7b | zero-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.832 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.825 ± 0.007 | 0.768 ± 0.009 | 0.870 ± 0.005 | ONNX int8 | 2.5 | 105.7 | 23 |
164
- | financial-tweets-ensemble | teacher ensemble of 3, zero-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct,mistralai/Mistral-7B-Instruct-v0.3,microsoft/Phi-3.5-mini-instruct` | 0.842 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.828 ± 0.006 | 0.778 ± 0.005 | 0.884 ± 0.007 | ONNX int8 | 1.6 | 99.4 | 23 |
165
- | financial-tweets-synthetic | zero-shot, 4-bit `Qwen/Qwen2.5-7B-Instruct` | 0.832 | `sentence-transformers/all-MiniLM-L6-v2` | 23 | 0.596 ± 0.037 | 0.571 ± 0.027 | 0.615 ± 0.045 | ONNX int8 | 1.6 | 110.4 | 23 |
166
-
167
- All latencies on the CPU with 2 threads.
168
- <!-- /results -->
169
-
170
- Here the 7B teacher is close to the student trained on all gold labels, and the student is close to the
171
- teacher. The ensemble adds a little. Few-shot examples hurt on this skewed task (see
172
- [Limitations](#limitations)). The student that saw only about 1,500 texts the teacher wrote itself is far
173
- behind the one trained on real tweets: generated texts are much less varied than real ones, so `synth` is a
174
- way to start without data, not a replacement for it. Full tables per run are in
175
- [`examples/financial-tweets/`](https://github.com/Andreas-Hum/tinydistill/tree/main/examples/financial-tweets).
176
-
177
- ### GoEmotions: multi-label
178
-
179
- Reddit comments with any of 27 emotions or neutral
180
- ([GoEmotions](https://huggingface.co/datasets/google-research-datasets/go_emotions), Apache 2.0), reproduced
181
- by [`examples/go-emotions/run.sh`](https://github.com/Andreas-Hum/tinydistill/blob/main/examples/go-emotions/run.sh).
182
- The teacher answers one yes/no question per label; accuracy is the share of texts with exactly the right set
183
- of labels.
184
-
185
- <!-- results: go-emotions/go-emotions-1.5b.md -->
186
- | Model | Device | Accuracy | Macro-F1 | Micro-F1 | Agreement with teacher | Latency, batch 1 (ms) | Latency, batch 32 (ms) | Throughput (texts/s) | Size (MB) | Speed-up, batch 1 |
187
- |---|---|---|---|---|---|---|---|---|---|---|
188
- | teacher, zero-shot | cpu | 0.004 | 0.191 | 0.186 | n/a | 9990.9 | n/a | 0.1 | 6175 | 1.0x |
189
- | teacher, zero-shot | cuda | 0.004 | 0.191 | 0.186 | n/a | 74.3 | 2131.6 | 15.0 | 3087 | 134.5x |
190
- | student, PyTorch | cpu | 0.002 ± 0.001 | 0.111 ± 0.004 | 0.141 ± 0.002 | 0.015 ± 0.002 | 5.4 | 116.3 | 275.2 | 91 | 1857.3x |
191
- | student, ONNX fp32 | cpu | 0.002 | 0.106 | 0.141 | 0.016 | 4.8 | 115.7 | 276.6 | 91 | 2072.9x |
192
- | student, ONNX int8 | cpu | 0.002 | 0.108 | 0.141 | 0.016 | 2.0 | 66.3 | 482.8 | 23 | 5026.0x |
193
- | baseline: student on 8 gold labels per class, no teacher | cpu | 0.000 ± 0.000 | 0.000 ± 0.000 | 0.000 ± 0.000 | 0.007 ± 0.000 | n/a | n/a | n/a | n/a | n/a |
194
- | upper bound: student on all gold labels | cpu | 0.225 ± 0.021 | 0.034 ± 0.019 | 0.320 ± 0.034 | 0.007 ± 0.000 | n/a | n/a | n/a | n/a | n/a |
195
-
196
- Task `go-emotions`, 5427 gold-labelled test texts. Teacher: `Qwen/Qwen2.5-1.5B-Instruct`. Student: `sentence-transformers/all-MiniLM-L6-v2`. The student learns from the teacher's soft labels on all 10000 training texts, plus cross-entropy on 8 gold labels per class (223 texts, weight 1.0). The baseline is the same student trained with cross-entropy on those 223 gold labels only, without the teacher. The upper bound is the same student trained with cross-entropy on all 10000 gold training labels and no teacher; its speed equals the student's. Values with ± are the mean and standard deviation over 3 training seeds; the ONNX rows are the exported first-seed model.
197
- CPU rows use 2 threads. Latency is the median over repeated runs. Size is the teacher's weights as loaded and the students' model files. The teacher packs several texts into one forward pass; it is timed at batch 32 only when that takes under three minutes, otherwise its throughput is 1 / batch-1 latency. Speed-up is relative to the teacher, zero-shot on cpu.
198
- <!-- /results -->
199
-
200
- This is a hard setting for a 1.5B teacher: it answers yes far too often, so exact-set accuracy is near zero,
201
- and the student inherits that. The run shows that multi-label tasks work end to end, not that a small
202
- teacher is good at them; use a larger teacher.
203
-
204
- ### A vLLM teacher agrees with the local one
205
-
206
- [`examples/vllm-smoke.sh`](https://github.com/Andreas-Hum/tinydistill/blob/main/examples/vllm-smoke.sh)
207
- labels 300 financial tweets with `Qwen/Qwen2.5-1.5B-Instruct` locally and through a vLLM server:
208
-
209
- <!-- results: vllm-smoke.md -->
210
- | Texts | Same top label | Largest difference in a label score | Mean difference |
211
- |---|---|---|---|
212
- | 300 | 100.0% | 0.611 | 0.0900 |
213
-
214
- Qwen2.5-1.5B-Instruct scoring 300 financial tweets locally (bf16, tinydistill) and through vLLM (prompt log-probabilities); label scores are log-probabilities, so small differences are numerical.
215
- <!-- /results -->
91
+ Python 3.11 or newer, on Linux, macOS or Windows. Labelling with a large teacher wants a GPU; training and
92
+ inference are fine on a CPU. For a CPU-only PyTorch, install it first:
93
+ `pip install torch --index-url https://download.pytorch.org/whl/cpu`.
216
94
 
217
95
  ## Quickstart
218
96
 
219
97
  ```sh
220
- pip install "tinydistill[hf]"
221
98
  curl -LO https://raw.githubusercontent.com/Andreas-Hum/tinydistill/main/examples/banking77/task.yaml
222
99
  tinydistill run task.yaml --data hf:PolyAI/banking77:train --test hf:PolyAI/banking77:test \
223
100
  --out runs/banking77 --int8
224
101
  tinydistill predict runs/banking77/onnx "I still haven't received my new card"
225
102
  ```
226
103
 
104
+ The same from Python, with the exported model:
105
+
106
+ ```python
107
+ from tinydistill import Predictor
108
+
109
+ clf = Predictor("runs/banking77/onnx") # or a model on the Hub: Predictor("hf:user/name")
110
+ clf.predict(["I still haven't received my new card"]) # a list of label names
111
+ ```
112
+
113
+ Or try it without installing anything: the [Colab notebook](https://colab.research.google.com/github/Andreas-Hum/tinydistill/blob/main/notebooks/quickstart.ipynb) distils a sentiment classifier in a
114
+ few minutes on a free GPU.
115
+
227
116
  Labelling the full dataset with the default teacher wants a GPU. For a quick look on a CPU, add
228
117
  `--teacher Qwen/Qwen2.5-0.5B-Instruct --limit 200 --test-limit 200`. `--limit` takes the first rows, and the
229
118
  Banking77 splits are sorted by intent, so a limited run only sees a few intents.
@@ -253,16 +142,80 @@ Banking77 splits are sorted by intent, so a limited run only sees a few intents.
253
142
  Data can be `.csv`, `.jsonl` or `hf:<dataset>[:<config>]:<split>` (needs the `hf` extra). Useful options:
254
143
  `--teacher` (any Hugging Face causal LM), `--load-4bit` (bitsandbytes, CUDA only: `pip install "tinydistill[4bit]"`),
255
144
  `--student` (any Hugging Face encoder), `--epochs`, `--lr`, `--device`, `--threads`, `--seed`, and `--cache-dir`
256
- (runs that share it label each text only once). See `tinydistill <command> --help`.
145
+ (runs that share it label each text only once, and a run whose texts are all cached does not load the
146
+ teacher at all). See `tinydistill <command> --help`.
257
147
 
258
148
  Long steps print their progress, speed and time left every few seconds and write it to `RUN/status.json`;
259
149
  `tinydistill status RUN` reads it from another terminal and tells you whether the process is still alive.
260
150
 
151
+ ## Results
152
+
153
+ Banking77 intent detection: 77 fine-grained classes, 3,080 held-out test texts. The students learn from the
154
+ teacher's soft labels on 10,003 training texts plus 8 gold labels per class. Latency is per text, one at a
155
+ time:
156
+
157
+ <!-- results: headline.md -->
158
+ | Model | Runs on | Test accuracy | Latency per text | Size | Speed-up |
159
+ |---|---|---|---|---|---|
160
+ | Teacher: `Qwen/Qwen2.5-7B-Instruct`, 4-bit, 8 examples per class in the prompt | RTX 4090 | 0.783 | 264 ms | 5.4 GB | 1x |
161
+ | Student: MiniLM-L6, int8 ONNX | CPU, 2 threads | 0.814 | 1.2 ms | 23 MB | 214x |
162
+ | Student: static embeddings, int8 ONNX | CPU, 2 threads | 0.802 | 0.1 ms | 8 MB | 3244x |
163
+ <!-- /results -->
164
+
165
+ - The students are **more accurate than their teacher**: learning its soft labels across 10,000 texts smooths
166
+ out some of its individual mistakes.
167
+ - On a CPU they are **hundreds of times faster** than the teacher on a GPU, at a fraction of its size.
168
+ - **The teacher sets the ceiling.** With a 1.5B teacher, or without examples in its prompt, the students are
169
+ much weaker; the same student trained on all 10,003 gold labels reaches 0.914.
170
+
171
+ [RESULTS.md](https://github.com/Andreas-Hum/tinydistill/blob/main/RESULTS.md) has every run: four teachers, five student models trained for 5 to 20 epochs, the gold-only baseline,
172
+ the upper bound and the cascade on Banking77, a teacher ensemble and synthetic texts on financial tweets,
173
+ multi-label GoEmotions, and a vLLM check. Every number comes from a saved results file written by the
174
+ scripts in [`examples/`](https://github.com/Andreas-Hum/tinydistill/tree/main/examples).
175
+
176
+ ## How it works
177
+
178
+ ```mermaid
179
+ flowchart LR
180
+ A["Task prompt +<br/>unlabelled texts"] --> B["LLM teacher(s)<br/>Qwen · Mistral · Phi · ..."]
181
+ S["Synthetic texts<br/>(synth)"] -.-> B
182
+ V["vLLM server"] -.-> B
183
+ K["A few gold labels<br/>per class"] -.-> B
184
+ K -.-> C
185
+ B -- "soft labels<br/>(ensembled)" --> C["Small encoder<br/>student"]
186
+ C --> D["ONNX / int8<br/>export"]
187
+ D --> E["Fast CPU<br/>inference"]
188
+ D --> W["Browser · server ·<br/>Hugging Face Hub"]
189
+ E -.-> F["Cascade: unsure<br/>texts to the LLM"]
190
+ ```
191
+
192
+ Each step is one command: `tinydistill label` (teacher), `train` (student), `export` (ONNX / int8) and
193
+ `predict` (inference), or `tinydistill run` for all of them. Dashed parts are optional: synthetic texts when
194
+ there is no unlabelled data, a vLLM server as the teacher, a few gold labels for the teacher's prompt and the
195
+ student's loss, and a cascade that hands the student's least confident texts to the LLM. The exported model
196
+ runs on a CPU, in a [browser](#deploying-cascade-server-browser), behind `tinydistill serve`, or from the
197
+ Hugging Face Hub.
198
+
199
+ ## Guide
200
+
201
+ ### Training the student
202
+
203
+ The student trains for `--epochs` passes over the training texts (default 10) on the KL divergence to the
204
+ teacher's soft labels, plus cross-entropy on any gold labels (`--gold-per-class`, `--gold-file`). On Banking77,
205
+ training longer mattered more than a bigger student: MiniLM-L6 reaches 0.729 after 5 epochs and 0.813 after 20
206
+ ([RESULTS.md](https://github.com/Andreas-Hum/tinydistill/blob/main/RESULTS.md#banking77-the-hard-case)).
207
+
208
+ To choose the number of epochs from the data, `--val-share 0.05` holds out 5% of the training texts (texts
209
+ without gold labels, so no labels are lost), measures the loss on them after every epoch, keeps the best epoch
210
+ and stops after `--patience` epochs (default 3) without improvement. No gold labels are needed: the held-out
211
+ loss is measured against the teacher's soft labels. `--seeds N` trains every student N times and reports the
212
+ mean and standard deviation.
213
+
261
214
  **Static student.** `--student static` (or `static:<encoder>`) trains a student with no attention at all: one
262
215
  vector per token, averaged, then a linear layer, started from the encoder's own token representations (as in
263
216
  Model2Vec). It is much smaller and faster than a transformer student. It ignores word order, so it can miss
264
- meaning that depends on it (negation, "A rather than B"); on Banking77 it beat the small transformer
265
- students and came close to mpnet (see the results). It exports to ONNX and runs with `predict` and `serve` like any other student, but
217
+ meaning that depends on it (negation, "A rather than B"); on Banking77 it came within about a point of
218
+ MiniLM students trained as long (see the results). It exports to ONNX and runs with `predict` and `serve` like any other student, but
266
219
  not in the browser page. Train it longer and at a higher learning rate, such as `--epochs 20 --lr 1e-3`.
267
220
 
268
221
  ### Choosing the teacher
@@ -307,7 +260,14 @@ using only the standard library. It listens on 127.0.0.1 unless you pass `--host
307
260
 
308
261
  **Browser.** `tinydistill web RUN/onnx --out site` writes a static page plus the model in the layout
309
262
  [transformers.js](https://github.com/huggingface/transformers.js) expects; the int8 model then runs in the
310
- visitor's browser with no server at all. Serve the folder with any static host (GitHub Pages works).
263
+ visitor's browser with no server at all. Below the answer, an "Inside the model" view shows each step of the inference
264
+ (tokens, the answer after each layer, attention, probabilities, which words matter, the teacher's soft labels
265
+ for the examples and the cascade), so the page also explains distillation. Serve the folder with any static host (GitHub Pages works).
266
+
267
+ **Hugging Face Hub.** `tinydistill push RUN/onnx --repo user/name` uploads the model with a model card. Anyone
268
+ can then use it without the run directory: `Predictor("hf:user/name")` in Python, or
269
+ `tinydistill predict hf:user/name "some text"`, `serve hf:user/name` and `web hf:user/name --out site` on the
270
+ command line.
311
271
 
312
272
  ### A few gold labels
313
273
 
@@ -459,7 +419,16 @@ The student minimises KL(teacher || student) over the training texts, plus `--ce
459
419
  cross-entropy on the K gold labels per class chosen by `--gold-per-class`, if any (standard knowledge
460
420
  distillation, Hinton et al., 2015).
461
421
 
462
- ## Limitations
422
+ ## When to use tinydistill, and when not
423
+
424
+ Use it when an LLM prompt already classifies your texts well enough, but it is too slow, too expensive or too
425
+ large to run on every text: tinydistill keeps most of its quality in a model that runs in milliseconds on a CPU.
426
+ Do not use it when the LLM itself is not good at the task (the student inherits its mistakes), when you have
427
+ enough gold labels to train a classifier directly, or when the task needs generated text rather than a choice
428
+ among labels.
429
+
430
+ ### Limitations
431
+
463
432
 
464
433
  - **The teacher caps the student.** The student learns to imitate the teacher, mistakes included. A small LLM
465
434
  choosing zero-shot among many fine-grained labels can be weak: Qwen2.5-0.5B-Instruct, for example, often
@@ -484,7 +453,7 @@ distillation, Hinton et al., 2015).
484
453
  own hardware.
485
454
  - ONNX export uses PyTorch's TorchScript-based exporter.
486
455
 
487
- ## Development
456
+ ## Contributing
488
457
 
489
458
  ```sh
490
459
  python -m venv .venv && . .venv/bin/activate
@@ -497,8 +466,30 @@ The tests run on a CPU in a few seconds with tiny random models from `hf-interna
497
466
  scores against a direct computation, the distillation loss and metrics against hand-computed values, cache
498
467
  resumption, that the ONNX export reproduces the PyTorch logits, and the whole CLI end to end.
499
468
 
500
- See [CONTRIBUTING.md](CONTRIBUTING.md) for how to propose changes, [SECURITY.md](SECURITY.md) for reporting
501
- vulnerabilities and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for the code of conduct.
469
+ See [CONTRIBUTING.md](https://github.com/Andreas-Hum/tinydistill/blob/main/CONTRIBUTING.md) for how to propose changes, [SECURITY.md](https://github.com/Andreas-Hum/tinydistill/blob/main/SECURITY.md) for reporting
470
+ vulnerabilities and [CODE_OF_CONDUCT.md](https://github.com/Andreas-Hum/tinydistill/blob/main/CODE_OF_CONDUCT.md) for the code of conduct.
471
+
472
+ ## Citation
473
+
474
+ If tinydistill helps your work, please cite it:
475
+
476
+ ```bibtex
477
+ @software{tinydistill,
478
+ author = {Andreas-Hum},
479
+ title = {tinydistill: distil an LLM classification prompt into a small, fast model},
480
+ url = {https://github.com/Andreas-Hum/tinydistill},
481
+ year = {2026},
482
+ }
483
+ ```
484
+
485
+ ## Star history
486
+
487
+ <a href="https://www.star-history.com/#Andreas-Hum/tinydistill&Date">
488
+ <picture>
489
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Andreas-Hum/tinydistill&type=Date&theme=dark">
490
+ <img alt="Star history of tinydistill" src="https://api.star-history.com/svg?repos=Andreas-Hum/tinydistill&type=Date" width="600">
491
+ </picture>
492
+ </a>
502
493
 
503
494
  ## References
504
495