tujia-llm 0.2.0__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.
- tujia_llm-0.2.0/LICENSE +21 -0
- tujia_llm-0.2.0/NOTICE +11 -0
- tujia_llm-0.2.0/PKG-INFO +164 -0
- tujia_llm-0.2.0/README.md +142 -0
- tujia_llm-0.2.0/pyproject.toml +37 -0
- tujia_llm-0.2.0/setup.cfg +4 -0
- tujia_llm-0.2.0/tujia_llm/__init__.py +5 -0
- tujia_llm-0.2.0/tujia_llm/__main__.py +4 -0
- tujia_llm-0.2.0/tujia_llm/backend_mlx.py +47 -0
- tujia_llm-0.2.0/tujia_llm/cli.py +102 -0
- tujia_llm-0.2.0/tujia_llm/config.py +65 -0
- tujia_llm-0.2.0/tujia_llm.egg-info/PKG-INFO +164 -0
- tujia_llm-0.2.0/tujia_llm.egg-info/SOURCES.txt +15 -0
- tujia_llm-0.2.0/tujia_llm.egg-info/dependency_links.txt +1 -0
- tujia_llm-0.2.0/tujia_llm.egg-info/entry_points.txt +2 -0
- tujia_llm-0.2.0/tujia_llm.egg-info/requires.txt +4 -0
- tujia_llm-0.2.0/tujia_llm.egg-info/top_level.txt +1 -0
tujia_llm-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ibaibing
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tujia_llm-0.2.0/NOTICE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Usage boundaries — tujia-llm
|
|
2
|
+
|
|
3
|
+
Code in this repository is MIT-licensed (see LICENSE).
|
|
4
|
+
|
|
5
|
+
Model weights — LoRA adapters and merged or quantized checkpoints — are provided
|
|
6
|
+
for research use only. No rights are granted to the training corpora, the source
|
|
7
|
+
recordings, or their speakers, and none of that material is distributed here.
|
|
8
|
+
|
|
9
|
+
The base model (Qwen/Qwen3-8B) keeps its own license (Apache-2.0) and must retain
|
|
10
|
+
its attribution wherever these weights or results derived from them are
|
|
11
|
+
redistributed or published.
|
tujia_llm-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tujia-llm
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Tujia language generation with a LoRA adapter on Qwen3-8B (MLX, Apple Silicon)
|
|
5
|
+
Author: ibaibing
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ibaibing/tujia-llm
|
|
8
|
+
Project-URL: Model (Hugging Face), https://huggingface.co/TujiaNLP/tujia-llm-8b-v0.2
|
|
9
|
+
Project-URL: Model (ModelScope), https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2
|
|
10
|
+
Keywords: tujia,low-resource,language,mlx,lora
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: NOTICE
|
|
19
|
+
Requires-Dist: huggingface_hub>=0.24
|
|
20
|
+
Requires-Dist: mlx-lm>=0.21; sys_platform == "darwin" and platform_machine == "arm64"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# tujia-llm
|
|
24
|
+
|
|
25
|
+
LoRA weights and evaluation for Tujia language generation. First public release: **v0.2** (research preview). v0.1 was internal only.
|
|
26
|
+
|
|
27
|
+
> **Status: research preview.** Suitable for draft lookup and short-sentence translation, not for
|
|
28
|
+
> authoritative output. Exact word-lookup accuracy is 5.8%; sentence similarity is 0.45.
|
|
29
|
+
> See [evaluation](docs/eval-v0.2.md) for details.
|
|
30
|
+
|
|
31
|
+
## Model
|
|
32
|
+
|
|
33
|
+
| Item | Value |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Base model | `Qwen/Qwen3-8B` (base, not instruct); MLX conversion `mlx-community/Qwen3-8B-bf16` |
|
|
36
|
+
| Method | LoRA, rank 16, 16 layers, scale 32, dropout 0.05 |
|
|
37
|
+
| Weight size | 74 MB (`adapters.safetensors`) |
|
|
38
|
+
| Training steps | 35,000 + 12,000 continued |
|
|
39
|
+
| Target variety | Northern Tujia, Longshan standard |
|
|
40
|
+
| License | Research use only; see [NOTICE](NOTICE) |
|
|
41
|
+
|
|
42
|
+
## Download
|
|
43
|
+
|
|
44
|
+
| Channel | Location |
|
|
45
|
+
|---|---|
|
|
46
|
+
| ModelScope (MLX) | [TujiaNLP/tujia-llm-8b-v0.2](https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2) |
|
|
47
|
+
| ModelScope (GGUF) | [TujiaNLP/tujia-llm-8b-v0.2-gguf](https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2-gguf) |
|
|
48
|
+
| Hugging Face | [MLX adapter](https://huggingface.co/TujiaNLP/tujia-llm-8b-v0.2); GGUF repo is metadata only |
|
|
49
|
+
| GitHub Releases | `github.com/ibaibing/tujia-llm/releases` → `tujia-llm-8b-v0.2.tar.gz` |
|
|
50
|
+
| PyPI | `pip install tujia-llm` (Apple Silicon) |
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
**Requirements:** macOS 13+, Apple Silicon, Python 3.10+.
|
|
55
|
+
|
|
56
|
+
| Memory | Flag | Download | Notes |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| 32 GB+ | default (`bf16`) | ~15 GB | Reference quality |
|
|
59
|
+
| 16 GB | `--precision 4bit` | ~4.5 GB | Runs, but output degrades visibly (repetition, tone errors) |
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install tujia-llm # after the PyPI release
|
|
63
|
+
pip install dist/tujia_llm-0.2.0-py3-none-any.whl # offline / local wheel
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The wheel contains code only. Weights (74 MB) and the base model are downloaded on first run.
|
|
67
|
+
|
|
68
|
+
## CLI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
tujia-llm info # backend, precision, memory
|
|
72
|
+
tujia-llm lookup 酒
|
|
73
|
+
tujia-llm translate "大家都过好日子。"
|
|
74
|
+
tujia-llm translate "大家都过好日子,土家人们都相好。" --split
|
|
75
|
+
tujia-llm translate "zɤ³⁵" --to zh
|
|
76
|
+
tujia-llm translate "zɤ³⁵" --to en
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Options: `--adapter` (local directory or HF repo id), `--precision auto|bf16|4bit`,
|
|
80
|
+
`--max-tokens`, `--json`.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
tujia-llm lookup 酒 --adapter /path/to/adapter-dir --json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For development, `python scripts/infer.py` offers the same prompts without installation.
|
|
87
|
+
|
|
88
|
+
## Windows / CPU (GGUF)
|
|
89
|
+
|
|
90
|
+
The MLX build is Apple Silicon only. For Windows, Linux, and Intel Macs use the GGUF build
|
|
91
|
+
with llama.cpp or Ollama — see [docs/windows-gguf.md](docs/windows-gguf.md).
|
|
92
|
+
|
|
93
|
+
| File | Size | Memory |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `gguf/tujia-llm-8b-v0.2-Q4_K_M.gguf` | 4.7 GB | 16 GB+ |
|
|
96
|
+
| `gguf/tujia-llm-8b-v0.2-f16.gguf` | 15 GB | 20 GB+ |
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
ollama create tujia -f gguf/Modelfile
|
|
100
|
+
ollama run tujia "「酒」用土家语怎么说?"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Capabilities and limits
|
|
104
|
+
|
|
105
|
+
**Works**
|
|
106
|
+
|
|
107
|
+
- Chinese word → Tujia form + IPA (reliable for high-frequency words)
|
|
108
|
+
- Chinese short sentence → Tujia sentence (stable up to roughly 8 syllables)
|
|
109
|
+
- Tujia sentence → English
|
|
110
|
+
|
|
111
|
+
**Does not work**
|
|
112
|
+
|
|
113
|
+
- Authoritative translation, publication-grade text, legal or medical use
|
|
114
|
+
- Folk songs, ritual language, and other low-resource registers
|
|
115
|
+
- Southern Tujia (Luxi) and non-Longshan varieties such as Baojing
|
|
116
|
+
- Out-of-vocabulary words: there is no refusal mechanism, so the model will invent forms
|
|
117
|
+
|
|
118
|
+
## Evaluation summary
|
|
119
|
+
|
|
120
|
+
| Metric | Base | v0.2 |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| Lexicon 500, first-candidate exact | 0% | 5.8% |
|
|
123
|
+
| Lexicon 500, edit similarity | 0.020* | 0.511 |
|
|
124
|
+
| Single-entry 250, similarity | 0.024* | 0.449 |
|
|
125
|
+
| Sentences 100, similarity (split decoding) | 0.082 | 0.451 |
|
|
126
|
+
| Song lyrics, 12 lines, similarity | — | 0.583 |
|
|
127
|
+
|
|
128
|
+
\* Base model evaluated on the first 100 items only, so not directly comparable to the full sets.
|
|
129
|
+
|
|
130
|
+
Full tables, dialect breakdowns, and failure modes: [docs/eval-v0.2.md](docs/eval-v0.2.md).
|
|
131
|
+
|
|
132
|
+
## Prompt format
|
|
133
|
+
|
|
134
|
+
The model was trained on four fixed templates. Keep them unchanged at inference time:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
「{Chinese word}」用土家语怎么说?
|
|
138
|
+
把这句话翻译成土家语:{Chinese sentence}
|
|
139
|
+
把这句话翻译成汉语:{Tujia sentence}
|
|
140
|
+
把这句话翻译成英语:{Tujia sentence}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Output uses mixed Ye pinyin / IPA notation with superscript tone digits, e.g. `zɤ³⁵`.
|
|
144
|
+
|
|
145
|
+
## Layout
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
scripts/infer.py inference example
|
|
149
|
+
docs/eval-v0.2.md evaluation report
|
|
150
|
+
release/ release config and upload script
|
|
151
|
+
MODEL_CARD.md model card
|
|
152
|
+
NOTICE usage boundaries
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Citation
|
|
156
|
+
|
|
157
|
+
```bibtex
|
|
158
|
+
@misc{tujia-llm-v0.1,
|
|
159
|
+
title = {tujia-llm: LoRA adapter for Tujia language generation (v0.2)},
|
|
160
|
+
author = {ibaibing},
|
|
161
|
+
year = {2026},
|
|
162
|
+
note = {Research preview}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# tujia-llm
|
|
2
|
+
|
|
3
|
+
LoRA weights and evaluation for Tujia language generation. First public release: **v0.2** (research preview). v0.1 was internal only.
|
|
4
|
+
|
|
5
|
+
> **Status: research preview.** Suitable for draft lookup and short-sentence translation, not for
|
|
6
|
+
> authoritative output. Exact word-lookup accuracy is 5.8%; sentence similarity is 0.45.
|
|
7
|
+
> See [evaluation](docs/eval-v0.2.md) for details.
|
|
8
|
+
|
|
9
|
+
## Model
|
|
10
|
+
|
|
11
|
+
| Item | Value |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Base model | `Qwen/Qwen3-8B` (base, not instruct); MLX conversion `mlx-community/Qwen3-8B-bf16` |
|
|
14
|
+
| Method | LoRA, rank 16, 16 layers, scale 32, dropout 0.05 |
|
|
15
|
+
| Weight size | 74 MB (`adapters.safetensors`) |
|
|
16
|
+
| Training steps | 35,000 + 12,000 continued |
|
|
17
|
+
| Target variety | Northern Tujia, Longshan standard |
|
|
18
|
+
| License | Research use only; see [NOTICE](NOTICE) |
|
|
19
|
+
|
|
20
|
+
## Download
|
|
21
|
+
|
|
22
|
+
| Channel | Location |
|
|
23
|
+
|---|---|
|
|
24
|
+
| ModelScope (MLX) | [TujiaNLP/tujia-llm-8b-v0.2](https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2) |
|
|
25
|
+
| ModelScope (GGUF) | [TujiaNLP/tujia-llm-8b-v0.2-gguf](https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2-gguf) |
|
|
26
|
+
| Hugging Face | [MLX adapter](https://huggingface.co/TujiaNLP/tujia-llm-8b-v0.2); GGUF repo is metadata only |
|
|
27
|
+
| GitHub Releases | `github.com/ibaibing/tujia-llm/releases` → `tujia-llm-8b-v0.2.tar.gz` |
|
|
28
|
+
| PyPI | `pip install tujia-llm` (Apple Silicon) |
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
**Requirements:** macOS 13+, Apple Silicon, Python 3.10+.
|
|
33
|
+
|
|
34
|
+
| Memory | Flag | Download | Notes |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| 32 GB+ | default (`bf16`) | ~15 GB | Reference quality |
|
|
37
|
+
| 16 GB | `--precision 4bit` | ~4.5 GB | Runs, but output degrades visibly (repetition, tone errors) |
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install tujia-llm # after the PyPI release
|
|
41
|
+
pip install dist/tujia_llm-0.2.0-py3-none-any.whl # offline / local wheel
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The wheel contains code only. Weights (74 MB) and the base model are downloaded on first run.
|
|
45
|
+
|
|
46
|
+
## CLI
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
tujia-llm info # backend, precision, memory
|
|
50
|
+
tujia-llm lookup 酒
|
|
51
|
+
tujia-llm translate "大家都过好日子。"
|
|
52
|
+
tujia-llm translate "大家都过好日子,土家人们都相好。" --split
|
|
53
|
+
tujia-llm translate "zɤ³⁵" --to zh
|
|
54
|
+
tujia-llm translate "zɤ³⁵" --to en
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Options: `--adapter` (local directory or HF repo id), `--precision auto|bf16|4bit`,
|
|
58
|
+
`--max-tokens`, `--json`.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
tujia-llm lookup 酒 --adapter /path/to/adapter-dir --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For development, `python scripts/infer.py` offers the same prompts without installation.
|
|
65
|
+
|
|
66
|
+
## Windows / CPU (GGUF)
|
|
67
|
+
|
|
68
|
+
The MLX build is Apple Silicon only. For Windows, Linux, and Intel Macs use the GGUF build
|
|
69
|
+
with llama.cpp or Ollama — see [docs/windows-gguf.md](docs/windows-gguf.md).
|
|
70
|
+
|
|
71
|
+
| File | Size | Memory |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `gguf/tujia-llm-8b-v0.2-Q4_K_M.gguf` | 4.7 GB | 16 GB+ |
|
|
74
|
+
| `gguf/tujia-llm-8b-v0.2-f16.gguf` | 15 GB | 20 GB+ |
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
ollama create tujia -f gguf/Modelfile
|
|
78
|
+
ollama run tujia "「酒」用土家语怎么说?"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Capabilities and limits
|
|
82
|
+
|
|
83
|
+
**Works**
|
|
84
|
+
|
|
85
|
+
- Chinese word → Tujia form + IPA (reliable for high-frequency words)
|
|
86
|
+
- Chinese short sentence → Tujia sentence (stable up to roughly 8 syllables)
|
|
87
|
+
- Tujia sentence → English
|
|
88
|
+
|
|
89
|
+
**Does not work**
|
|
90
|
+
|
|
91
|
+
- Authoritative translation, publication-grade text, legal or medical use
|
|
92
|
+
- Folk songs, ritual language, and other low-resource registers
|
|
93
|
+
- Southern Tujia (Luxi) and non-Longshan varieties such as Baojing
|
|
94
|
+
- Out-of-vocabulary words: there is no refusal mechanism, so the model will invent forms
|
|
95
|
+
|
|
96
|
+
## Evaluation summary
|
|
97
|
+
|
|
98
|
+
| Metric | Base | v0.2 |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| Lexicon 500, first-candidate exact | 0% | 5.8% |
|
|
101
|
+
| Lexicon 500, edit similarity | 0.020* | 0.511 |
|
|
102
|
+
| Single-entry 250, similarity | 0.024* | 0.449 |
|
|
103
|
+
| Sentences 100, similarity (split decoding) | 0.082 | 0.451 |
|
|
104
|
+
| Song lyrics, 12 lines, similarity | — | 0.583 |
|
|
105
|
+
|
|
106
|
+
\* Base model evaluated on the first 100 items only, so not directly comparable to the full sets.
|
|
107
|
+
|
|
108
|
+
Full tables, dialect breakdowns, and failure modes: [docs/eval-v0.2.md](docs/eval-v0.2.md).
|
|
109
|
+
|
|
110
|
+
## Prompt format
|
|
111
|
+
|
|
112
|
+
The model was trained on four fixed templates. Keep them unchanged at inference time:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
「{Chinese word}」用土家语怎么说?
|
|
116
|
+
把这句话翻译成土家语:{Chinese sentence}
|
|
117
|
+
把这句话翻译成汉语:{Tujia sentence}
|
|
118
|
+
把这句话翻译成英语:{Tujia sentence}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Output uses mixed Ye pinyin / IPA notation with superscript tone digits, e.g. `zɤ³⁵`.
|
|
122
|
+
|
|
123
|
+
## Layout
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
scripts/infer.py inference example
|
|
127
|
+
docs/eval-v0.2.md evaluation report
|
|
128
|
+
release/ release config and upload script
|
|
129
|
+
MODEL_CARD.md model card
|
|
130
|
+
NOTICE usage boundaries
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Citation
|
|
134
|
+
|
|
135
|
+
```bibtex
|
|
136
|
+
@misc{tujia-llm-v0.1,
|
|
137
|
+
title = {tujia-llm: LoRA adapter for Tujia language generation (v0.2)},
|
|
138
|
+
author = {ibaibing},
|
|
139
|
+
year = {2026},
|
|
140
|
+
note = {Research preview}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tujia-llm"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Tujia language generation with a LoRA adapter on Qwen3-8B (MLX, Apple Silicon)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "ibaibing" }]
|
|
13
|
+
keywords = ["tujia", "low-resource", "language", "mlx", "lora"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: MacOS :: MacOS X",
|
|
18
|
+
"Topic :: Text Processing :: Linguistic",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"huggingface_hub>=0.24",
|
|
22
|
+
"mlx-lm>=0.21; sys_platform == 'darwin' and platform_machine == 'arm64'",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/ibaibing/tujia-llm"
|
|
27
|
+
"Model (Hugging Face)" = "https://huggingface.co/TujiaNLP/tujia-llm-8b-v0.2"
|
|
28
|
+
"Model (ModelScope)" = "https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
tujia-llm = "tujia_llm.cli:main"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
include = ["tujia_llm*"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""MLX inference backend."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MlxBackend:
|
|
8
|
+
def __init__(self, base: str, adapter: str, max_tokens: int = 96):
|
|
9
|
+
from mlx_lm import generate, load
|
|
10
|
+
|
|
11
|
+
self._generate = generate
|
|
12
|
+
self.max_tokens = max_tokens
|
|
13
|
+
self.model, self.tok = load(base, adapter_path=adapter)
|
|
14
|
+
|
|
15
|
+
def ask(self, prompt: str, max_tokens: int | None = None) -> str:
|
|
16
|
+
messages = [{"role": "user", "content": prompt}]
|
|
17
|
+
templated = self.tok.apply_chat_template(
|
|
18
|
+
messages, add_generation_prompt=True, enable_thinking=False
|
|
19
|
+
)
|
|
20
|
+
out = self._generate(
|
|
21
|
+
self.model,
|
|
22
|
+
self.tok,
|
|
23
|
+
prompt=templated,
|
|
24
|
+
max_tokens=max_tokens or self.max_tokens,
|
|
25
|
+
verbose=False,
|
|
26
|
+
)
|
|
27
|
+
return re.sub(r"^.*?</think>", "", out, flags=re.S).strip()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_adapter(spec: str | None, default_repo: str) -> str:
|
|
31
|
+
"""Accept a local directory, a local file, or a Hugging Face repo id.
|
|
32
|
+
|
|
33
|
+
Cached snapshots are preferred so repeated runs work offline.
|
|
34
|
+
"""
|
|
35
|
+
from huggingface_hub import snapshot_download
|
|
36
|
+
|
|
37
|
+
if spec:
|
|
38
|
+
p = Path(spec).expanduser()
|
|
39
|
+
if p.exists():
|
|
40
|
+
return str(p)
|
|
41
|
+
|
|
42
|
+
repo = spec or default_repo
|
|
43
|
+
kwargs = dict(repo_id=repo, allow_patterns=["*.safetensors", "*.json"])
|
|
44
|
+
try:
|
|
45
|
+
return snapshot_download(local_files_only=True, **kwargs)
|
|
46
|
+
except Exception:
|
|
47
|
+
return snapshot_download(**kwargs)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Command-line interface."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .config import (
|
|
11
|
+
DEFAULT_REPO,
|
|
12
|
+
TPL_LOOKUP,
|
|
13
|
+
TPL_TJ2EN,
|
|
14
|
+
TPL_TJ2ZH,
|
|
15
|
+
TPL_ZH2TJ,
|
|
16
|
+
require_mlx,
|
|
17
|
+
settings,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _common() -> argparse.ArgumentParser:
|
|
22
|
+
p = argparse.ArgumentParser(add_help=False)
|
|
23
|
+
p.add_argument("--adapter", default=argparse.SUPPRESS,
|
|
24
|
+
help="local adapter directory/file, or a Hugging Face repo id")
|
|
25
|
+
p.add_argument("--precision", choices=["auto", "bf16", "4bit"], default=argparse.SUPPRESS,
|
|
26
|
+
help="auto picks bf16 on machines with 32 GB or more")
|
|
27
|
+
p.add_argument("--max-tokens", type=int, default=argparse.SUPPRESS)
|
|
28
|
+
p.add_argument("--json", action="store_true", default=argparse.SUPPRESS,
|
|
29
|
+
help="machine-readable output")
|
|
30
|
+
return p
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
34
|
+
p = argparse.ArgumentParser(
|
|
35
|
+
prog="tujia-llm",
|
|
36
|
+
description="Tujia language generation (research preview).",
|
|
37
|
+
)
|
|
38
|
+
p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
39
|
+
|
|
40
|
+
common = _common()
|
|
41
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
42
|
+
|
|
43
|
+
lk = sub.add_parser("lookup", parents=[common], help="Chinese word to Tujia form")
|
|
44
|
+
lk.add_argument("word")
|
|
45
|
+
|
|
46
|
+
tr = sub.add_parser("translate", parents=[common], help="translate a sentence")
|
|
47
|
+
tr.add_argument("text")
|
|
48
|
+
tr.add_argument("--to", choices=["tj", "zh", "en"], default="tj",
|
|
49
|
+
help="target language: tj (Tujia), zh (Chinese), en (English)")
|
|
50
|
+
tr.add_argument("--split", action="store_true",
|
|
51
|
+
help="split long Chinese sentences at punctuation before translating")
|
|
52
|
+
|
|
53
|
+
sub.add_parser("info", parents=[common], help="show backend and model settings")
|
|
54
|
+
return p
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _split_zh(text: str) -> list[str]:
|
|
58
|
+
return [x for x in re.split(r"(?<=[,。!?;])", text) if x.strip()]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def run(args) -> dict:
|
|
62
|
+
precision = getattr(args, "precision", "auto")
|
|
63
|
+
max_tokens = getattr(args, "max_tokens", 96)
|
|
64
|
+
adapter = getattr(args, "adapter", "") or os.environ.get("TUJIA_LLM_ADAPTER", "")
|
|
65
|
+
|
|
66
|
+
cfg = settings(precision, DEFAULT_REPO)
|
|
67
|
+
|
|
68
|
+
if args.cmd == "info":
|
|
69
|
+
return {
|
|
70
|
+
"version": __version__,
|
|
71
|
+
"base": cfg.base,
|
|
72
|
+
"precision": cfg.precision,
|
|
73
|
+
"ram_gb": cfg.ram_gb,
|
|
74
|
+
"adapter": adapter or cfg.repo,
|
|
75
|
+
"platform": f"{sys.platform}/{os.uname().machine}",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
require_mlx()
|
|
79
|
+
from .backend_mlx import MlxBackend, resolve_adapter
|
|
80
|
+
|
|
81
|
+
backend = MlxBackend(cfg.base, resolve_adapter(adapter or None, cfg.repo),
|
|
82
|
+
max_tokens=max_tokens)
|
|
83
|
+
|
|
84
|
+
if args.cmd == "lookup":
|
|
85
|
+
prompt = TPL_LOOKUP.format(zh=args.word)
|
|
86
|
+
return {"input": args.word, "output": backend.ask(prompt)}
|
|
87
|
+
|
|
88
|
+
tpl = {"tj": TPL_ZH2TJ, "zh": TPL_TJ2ZH, "en": TPL_TJ2EN}[args.to]
|
|
89
|
+
if args.to == "tj" and args.split and len(args.text) > 18:
|
|
90
|
+
parts = [backend.ask(tpl.format(text=s), 64) for s in _split_zh(args.text)]
|
|
91
|
+
return {"input": args.text, "output": " ".join(parts), "split": len(parts)}
|
|
92
|
+
|
|
93
|
+
return {"input": args.text, "output": backend.ask(tpl.format(text=args.text))}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv=None) -> None:
|
|
97
|
+
args = build_parser().parse_args(argv)
|
|
98
|
+
result = run(args)
|
|
99
|
+
if getattr(args, "json", False):
|
|
100
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
101
|
+
else:
|
|
102
|
+
print(result.get("output", result))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Configuration and local environment helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
DEFAULT_REPO = "TujiaNLP/tujia-llm-8b-v0.2"
|
|
9
|
+
BASE_BF16 = "mlx-community/Qwen3-8B-bf16"
|
|
10
|
+
BASE_4BIT = "mlx-community/Qwen3-8B-4bit"
|
|
11
|
+
|
|
12
|
+
# Prompt templates must match training exactly.
|
|
13
|
+
TPL_LOOKUP = "「{zh}」用土家语怎么说?"
|
|
14
|
+
TPL_ZH2TJ = "把这句话翻译成土家语:{text}"
|
|
15
|
+
TPL_TJ2ZH = "把这句话翻译成汉语:{text}"
|
|
16
|
+
TPL_TJ2EN = "把这句话翻译成英语:{text}"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Settings:
|
|
21
|
+
repo: str
|
|
22
|
+
base: str
|
|
23
|
+
precision: str
|
|
24
|
+
ram_gb: float
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def total_ram_gb() -> float:
|
|
28
|
+
"""Best-effort physical memory size in GiB."""
|
|
29
|
+
if sys.platform == "darwin":
|
|
30
|
+
try:
|
|
31
|
+
out = subprocess.check_output(["sysctl", "-n", "hw.memsize"], text=True)
|
|
32
|
+
return int(out.strip()) / 2**30
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
try:
|
|
36
|
+
pages = os.sysconf("SC_PHYS_PAGES")
|
|
37
|
+
size = os.sysconf("SC_PAGE_SIZE")
|
|
38
|
+
return pages * size / 2**30
|
|
39
|
+
except Exception:
|
|
40
|
+
return 0.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def pick_precision(requested: str) -> str:
|
|
44
|
+
if requested in ("bf16", "4bit"):
|
|
45
|
+
return requested
|
|
46
|
+
# 8B bf16 needs roughly 16 GB for weights plus KV cache.
|
|
47
|
+
return "bf16" if total_ram_gb() >= 32 else "4bit"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def base_model(precision: str) -> str:
|
|
51
|
+
return BASE_BF16 if precision == "bf16" else BASE_4BIT
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def settings(precision: str = "auto", repo: str = DEFAULT_REPO) -> Settings:
|
|
55
|
+
p = pick_precision(precision)
|
|
56
|
+
return Settings(repo=repo, base=base_model(p), precision=p, ram_gb=round(total_ram_gb(), 1))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def require_mlx() -> None:
|
|
60
|
+
if sys.platform != "darwin" or os.uname().machine != "arm64":
|
|
61
|
+
raise SystemExit(
|
|
62
|
+
"This build requires Apple Silicon (macOS arm64) because the adapter is in MLX "
|
|
63
|
+
"format. On other platforms, use the GGUF build with llama.cpp or Ollama "
|
|
64
|
+
"(see the README)."
|
|
65
|
+
)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tujia-llm
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Tujia language generation with a LoRA adapter on Qwen3-8B (MLX, Apple Silicon)
|
|
5
|
+
Author: ibaibing
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ibaibing/tujia-llm
|
|
8
|
+
Project-URL: Model (Hugging Face), https://huggingface.co/TujiaNLP/tujia-llm-8b-v0.2
|
|
9
|
+
Project-URL: Model (ModelScope), https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2
|
|
10
|
+
Keywords: tujia,low-resource,language,mlx,lora
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: NOTICE
|
|
19
|
+
Requires-Dist: huggingface_hub>=0.24
|
|
20
|
+
Requires-Dist: mlx-lm>=0.21; sys_platform == "darwin" and platform_machine == "arm64"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# tujia-llm
|
|
24
|
+
|
|
25
|
+
LoRA weights and evaluation for Tujia language generation. First public release: **v0.2** (research preview). v0.1 was internal only.
|
|
26
|
+
|
|
27
|
+
> **Status: research preview.** Suitable for draft lookup and short-sentence translation, not for
|
|
28
|
+
> authoritative output. Exact word-lookup accuracy is 5.8%; sentence similarity is 0.45.
|
|
29
|
+
> See [evaluation](docs/eval-v0.2.md) for details.
|
|
30
|
+
|
|
31
|
+
## Model
|
|
32
|
+
|
|
33
|
+
| Item | Value |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Base model | `Qwen/Qwen3-8B` (base, not instruct); MLX conversion `mlx-community/Qwen3-8B-bf16` |
|
|
36
|
+
| Method | LoRA, rank 16, 16 layers, scale 32, dropout 0.05 |
|
|
37
|
+
| Weight size | 74 MB (`adapters.safetensors`) |
|
|
38
|
+
| Training steps | 35,000 + 12,000 continued |
|
|
39
|
+
| Target variety | Northern Tujia, Longshan standard |
|
|
40
|
+
| License | Research use only; see [NOTICE](NOTICE) |
|
|
41
|
+
|
|
42
|
+
## Download
|
|
43
|
+
|
|
44
|
+
| Channel | Location |
|
|
45
|
+
|---|---|
|
|
46
|
+
| ModelScope (MLX) | [TujiaNLP/tujia-llm-8b-v0.2](https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2) |
|
|
47
|
+
| ModelScope (GGUF) | [TujiaNLP/tujia-llm-8b-v0.2-gguf](https://modelscope.cn/models/TujiaNLP/tujia-llm-8b-v0.2-gguf) |
|
|
48
|
+
| Hugging Face | [MLX adapter](https://huggingface.co/TujiaNLP/tujia-llm-8b-v0.2); GGUF repo is metadata only |
|
|
49
|
+
| GitHub Releases | `github.com/ibaibing/tujia-llm/releases` → `tujia-llm-8b-v0.2.tar.gz` |
|
|
50
|
+
| PyPI | `pip install tujia-llm` (Apple Silicon) |
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
**Requirements:** macOS 13+, Apple Silicon, Python 3.10+.
|
|
55
|
+
|
|
56
|
+
| Memory | Flag | Download | Notes |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| 32 GB+ | default (`bf16`) | ~15 GB | Reference quality |
|
|
59
|
+
| 16 GB | `--precision 4bit` | ~4.5 GB | Runs, but output degrades visibly (repetition, tone errors) |
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install tujia-llm # after the PyPI release
|
|
63
|
+
pip install dist/tujia_llm-0.2.0-py3-none-any.whl # offline / local wheel
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The wheel contains code only. Weights (74 MB) and the base model are downloaded on first run.
|
|
67
|
+
|
|
68
|
+
## CLI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
tujia-llm info # backend, precision, memory
|
|
72
|
+
tujia-llm lookup 酒
|
|
73
|
+
tujia-llm translate "大家都过好日子。"
|
|
74
|
+
tujia-llm translate "大家都过好日子,土家人们都相好。" --split
|
|
75
|
+
tujia-llm translate "zɤ³⁵" --to zh
|
|
76
|
+
tujia-llm translate "zɤ³⁵" --to en
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Options: `--adapter` (local directory or HF repo id), `--precision auto|bf16|4bit`,
|
|
80
|
+
`--max-tokens`, `--json`.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
tujia-llm lookup 酒 --adapter /path/to/adapter-dir --json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For development, `python scripts/infer.py` offers the same prompts without installation.
|
|
87
|
+
|
|
88
|
+
## Windows / CPU (GGUF)
|
|
89
|
+
|
|
90
|
+
The MLX build is Apple Silicon only. For Windows, Linux, and Intel Macs use the GGUF build
|
|
91
|
+
with llama.cpp or Ollama — see [docs/windows-gguf.md](docs/windows-gguf.md).
|
|
92
|
+
|
|
93
|
+
| File | Size | Memory |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `gguf/tujia-llm-8b-v0.2-Q4_K_M.gguf` | 4.7 GB | 16 GB+ |
|
|
96
|
+
| `gguf/tujia-llm-8b-v0.2-f16.gguf` | 15 GB | 20 GB+ |
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
ollama create tujia -f gguf/Modelfile
|
|
100
|
+
ollama run tujia "「酒」用土家语怎么说?"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Capabilities and limits
|
|
104
|
+
|
|
105
|
+
**Works**
|
|
106
|
+
|
|
107
|
+
- Chinese word → Tujia form + IPA (reliable for high-frequency words)
|
|
108
|
+
- Chinese short sentence → Tujia sentence (stable up to roughly 8 syllables)
|
|
109
|
+
- Tujia sentence → English
|
|
110
|
+
|
|
111
|
+
**Does not work**
|
|
112
|
+
|
|
113
|
+
- Authoritative translation, publication-grade text, legal or medical use
|
|
114
|
+
- Folk songs, ritual language, and other low-resource registers
|
|
115
|
+
- Southern Tujia (Luxi) and non-Longshan varieties such as Baojing
|
|
116
|
+
- Out-of-vocabulary words: there is no refusal mechanism, so the model will invent forms
|
|
117
|
+
|
|
118
|
+
## Evaluation summary
|
|
119
|
+
|
|
120
|
+
| Metric | Base | v0.2 |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| Lexicon 500, first-candidate exact | 0% | 5.8% |
|
|
123
|
+
| Lexicon 500, edit similarity | 0.020* | 0.511 |
|
|
124
|
+
| Single-entry 250, similarity | 0.024* | 0.449 |
|
|
125
|
+
| Sentences 100, similarity (split decoding) | 0.082 | 0.451 |
|
|
126
|
+
| Song lyrics, 12 lines, similarity | — | 0.583 |
|
|
127
|
+
|
|
128
|
+
\* Base model evaluated on the first 100 items only, so not directly comparable to the full sets.
|
|
129
|
+
|
|
130
|
+
Full tables, dialect breakdowns, and failure modes: [docs/eval-v0.2.md](docs/eval-v0.2.md).
|
|
131
|
+
|
|
132
|
+
## Prompt format
|
|
133
|
+
|
|
134
|
+
The model was trained on four fixed templates. Keep them unchanged at inference time:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
「{Chinese word}」用土家语怎么说?
|
|
138
|
+
把这句话翻译成土家语:{Chinese sentence}
|
|
139
|
+
把这句话翻译成汉语:{Tujia sentence}
|
|
140
|
+
把这句话翻译成英语:{Tujia sentence}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Output uses mixed Ye pinyin / IPA notation with superscript tone digits, e.g. `zɤ³⁵`.
|
|
144
|
+
|
|
145
|
+
## Layout
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
scripts/infer.py inference example
|
|
149
|
+
docs/eval-v0.2.md evaluation report
|
|
150
|
+
release/ release config and upload script
|
|
151
|
+
MODEL_CARD.md model card
|
|
152
|
+
NOTICE usage boundaries
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Citation
|
|
156
|
+
|
|
157
|
+
```bibtex
|
|
158
|
+
@misc{tujia-llm-v0.1,
|
|
159
|
+
title = {tujia-llm: LoRA adapter for Tujia language generation (v0.2)},
|
|
160
|
+
author = {ibaibing},
|
|
161
|
+
year = {2026},
|
|
162
|
+
note = {Research preview}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
NOTICE
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
tujia_llm/__init__.py
|
|
6
|
+
tujia_llm/__main__.py
|
|
7
|
+
tujia_llm/backend_mlx.py
|
|
8
|
+
tujia_llm/cli.py
|
|
9
|
+
tujia_llm/config.py
|
|
10
|
+
tujia_llm.egg-info/PKG-INFO
|
|
11
|
+
tujia_llm.egg-info/SOURCES.txt
|
|
12
|
+
tujia_llm.egg-info/dependency_links.txt
|
|
13
|
+
tujia_llm.egg-info/entry_points.txt
|
|
14
|
+
tujia_llm.egg-info/requires.txt
|
|
15
|
+
tujia_llm.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tujia_llm
|