termux-bitnet 1.1.5__py3-none-any.whl
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.
- termux_bitnet/__init__.py +19 -0
- termux_bitnet/adapter.py +41 -0
- termux_bitnet/cli.py +323 -0
- termux_bitnet/config.py +63 -0
- termux_bitnet/control/__init__.py +3 -0
- termux_bitnet/control/component.py +387 -0
- termux_bitnet/control/errors.py +59 -0
- termux_bitnet/control/status.py +18 -0
- termux_bitnet/downloader.py +151 -0
- termux_bitnet/engine.py +493 -0
- termux_bitnet/exceptions.py +13 -0
- termux_bitnet/hardware.py +127 -0
- termux_bitnet/installer.py +71 -0
- termux_bitnet/server.py +341 -0
- termux_bitnet-1.1.5.dist-info/METADATA +227 -0
- termux_bitnet-1.1.5.dist-info/RECORD +20 -0
- termux_bitnet-1.1.5.dist-info/WHEEL +5 -0
- termux_bitnet-1.1.5.dist-info/entry_points.txt +5 -0
- termux_bitnet-1.1.5.dist-info/licenses/LICENSE +17 -0
- termux_bitnet-1.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""termux-bitnet: High-performance 1.58-bit BitNet Inference SDK for Android Termux & ARM64."""
|
|
2
|
+
|
|
3
|
+
from termux_bitnet.config import BitNetConfig, GenerationMetrics
|
|
4
|
+
from termux_bitnet.engine import BitNetEngine
|
|
5
|
+
from termux_bitnet.hardware import detect_hardware, print_hardware_summary
|
|
6
|
+
from termux_bitnet.downloader import download_model, AVAILABLE_MODELS
|
|
7
|
+
|
|
8
|
+
__version__ = "1.1.5"
|
|
9
|
+
__author__ = "uno-km"
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BitNetConfig",
|
|
13
|
+
"GenerationMetrics",
|
|
14
|
+
"BitNetEngine",
|
|
15
|
+
"detect_hardware",
|
|
16
|
+
"print_hardware_summary",
|
|
17
|
+
"download_model",
|
|
18
|
+
"AVAILABLE_MODELS",
|
|
19
|
+
]
|
termux_bitnet/adapter.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
termux_bitnet.adapter
|
|
3
|
+
======================
|
|
4
|
+
AMEVA Component Protocol v1 — Orchestrator Adapter (v0.8.1 호환)
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any, AsyncIterator
|
|
9
|
+
|
|
10
|
+
from ameva_component.adapter_base import BaseOrchestratorAdapter
|
|
11
|
+
from ameva_component.exceptions import OperationNotSupported
|
|
12
|
+
from termux_bitnet.control.component import BitNetControl
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BitNetOrchestratorAdapter(BaseOrchestratorAdapter):
|
|
16
|
+
"""BitNet Orchestrator Adapter.
|
|
17
|
+
|
|
18
|
+
BitNet CLI 및 C ABI inference는 내장 HTTP 서버를 통해 수행됩니다.
|
|
19
|
+
infer()는 OPERATION_NOT_SUPPORTED — BitNet 서버 URL을 직접 사용하십시오.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
COMPONENT_ID = "termux-bitnet"
|
|
23
|
+
|
|
24
|
+
def __init__(self, control: BitNetControl | None = None) -> None:
|
|
25
|
+
self._control = control or BitNetControl()
|
|
26
|
+
|
|
27
|
+
async def infer(self, request: dict[str, Any]) -> AsyncIterator[dict[str, Any]]:
|
|
28
|
+
"""BitNet inference는 내장 HTTP 서버(port 8080)를 통해 수행됩니다.
|
|
29
|
+
Orchestrator 직접 streaming은 미지원 — OperationNotSupported를 발생시킵니다.
|
|
30
|
+
|
|
31
|
+
P0-2: yield로 오류 객체를 반환하면 상위 소비자가 정상 Frame으로 처리할 수 있어
|
|
32
|
+
raise 방식으로 변경합니다.
|
|
33
|
+
"""
|
|
34
|
+
raise OperationNotSupported(operation="infer", component_id=self.COMPONENT_ID)
|
|
35
|
+
# AsyncIterator 타입 시그니처 충족을 위해 도달 불가 yield 유지
|
|
36
|
+
yield # type: ignore[misc]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_adapter() -> BitNetOrchestratorAdapter:
|
|
40
|
+
"""Entry Point Factory. 오케스트레이터가 ameva.components 그룹에서 호출합니다."""
|
|
41
|
+
return BitNetOrchestratorAdapter()
|
termux_bitnet/cli.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Command Line Interface for termux-bitnet (Strict Fail-Fast Protocol)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import argparse
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from termux_bitnet import __version__
|
|
10
|
+
from termux_bitnet.config import BitNetConfig
|
|
11
|
+
from termux_bitnet.engine import BitNetEngine
|
|
12
|
+
from termux_bitnet.hardware import print_hardware_summary, detect_hardware
|
|
13
|
+
from termux_bitnet.downloader import download_model, AVAILABLE_MODELS
|
|
14
|
+
from termux_bitnet.server import run_server
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_catalog_help():
|
|
18
|
+
print("\n[Available Official BitNet Models]", file=sys.stderr)
|
|
19
|
+
print(" 1. bitnet-2b : Microsoft BitNet 2B-4T (1.13 GB, i2_s)", file=sys.stderr)
|
|
20
|
+
print(" Command : termux-bitnet download bitnet-2b", file=sys.stderr)
|
|
21
|
+
print(" 2. bitnet-large : BitNet b1.58 Large 0.7B (700 MB, i2_s)", file=sys.stderr)
|
|
22
|
+
print(" Command : termux-bitnet download bitnet-large", file=sys.stderr)
|
|
23
|
+
print(" 3. bitnet-3b : BitNet b1.58 3B (2.4 GB, i2_s)", file=sys.stderr)
|
|
24
|
+
print(" Command : termux-bitnet download bitnet-3b", file=sys.stderr)
|
|
25
|
+
print(" 4. bitnet-3b-q4 : BitNet b1.58 3B Q4_K_M (1.8 GB)", file=sys.stderr)
|
|
26
|
+
print(" Command : termux-bitnet download bitnet-3b-q4", file=sys.stderr)
|
|
27
|
+
print("\nOfficial Hugging Face Repositories:", file=sys.stderr)
|
|
28
|
+
print(" - https://huggingface.co/1bitLLM/bitnet_b1_58-large-GGUF", file=sys.stderr)
|
|
29
|
+
print(" - https://huggingface.co/1bitLLM/bitnet_b1_58-3B-GGUF", file=sys.stderr)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def validate_model_path_or_exit(model_path: str = None) -> str:
|
|
33
|
+
if model_path and model_path.strip():
|
|
34
|
+
expanded = os.path.abspath(os.path.expanduser(model_path))
|
|
35
|
+
if os.path.isfile(expanded):
|
|
36
|
+
return expanded
|
|
37
|
+
print(f"[termux-bitnet ERROR] Model file not found at path: '{expanded}'", file=sys.stderr)
|
|
38
|
+
print_catalog_help()
|
|
39
|
+
sys.exit(10)
|
|
40
|
+
|
|
41
|
+
# Auto-discover in cache directory
|
|
42
|
+
cache_dir = Path.home() / ".cache" / "termux-bitnet" / "models"
|
|
43
|
+
if cache_dir.exists():
|
|
44
|
+
for preferred in ["bitnet_b1_58-large.Q4_0.gguf", "bitnet-large.gguf", "bitnet-2b-ggml-model-i2_s.gguf"]:
|
|
45
|
+
p = cache_dir / preferred
|
|
46
|
+
if p.exists() and p.is_file():
|
|
47
|
+
return str(p)
|
|
48
|
+
ggufs = list(cache_dir.glob("*.gguf"))
|
|
49
|
+
if ggufs:
|
|
50
|
+
return str(ggufs[0])
|
|
51
|
+
|
|
52
|
+
print("[termux-bitnet ERROR] No model file specified and no cached models found. Use -m or run: termux-bitnet download", file=sys.stderr)
|
|
53
|
+
print_catalog_help()
|
|
54
|
+
sys.exit(10)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_info(args):
|
|
58
|
+
print_hardware_summary()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cmd_download(args):
|
|
62
|
+
download_model(args.model_name, output_dir=args.output_dir)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cmd_run(args):
|
|
66
|
+
# 1. Validate Prompt
|
|
67
|
+
prompt_text = args.prompt
|
|
68
|
+
if args.file:
|
|
69
|
+
file_path = os.path.abspath(os.path.expanduser(args.file))
|
|
70
|
+
if not os.path.isfile(file_path):
|
|
71
|
+
print(f"[termux-bitnet ERROR] Prompt file not found: '{file_path}'", file=sys.stderr)
|
|
72
|
+
sys.exit(2)
|
|
73
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
74
|
+
prompt_text = f.read()
|
|
75
|
+
|
|
76
|
+
if not prompt_text or not prompt_text.strip():
|
|
77
|
+
print("[termux-bitnet ERROR] No input prompt specified. Use -p / --prompt \"<text>\" or -f / --file <path>.", file=sys.stderr)
|
|
78
|
+
print("Example:\n termux-bitnet run -m ~/.cache/termux-bitnet/models/bitnet-2b.gguf -p \"Explain 1-bit LLM:\"", file=sys.stderr)
|
|
79
|
+
sys.exit(2)
|
|
80
|
+
|
|
81
|
+
# 2. Validate Model
|
|
82
|
+
resolved_model = validate_model_path_or_exit(args.model)
|
|
83
|
+
|
|
84
|
+
config = BitNetConfig(
|
|
85
|
+
model_path=resolved_model,
|
|
86
|
+
system_prompt=args.system_prompt or "",
|
|
87
|
+
stop_tokens=args.stop or "",
|
|
88
|
+
device=getattr(args, "device", "auto"),
|
|
89
|
+
n_threads=args.threads,
|
|
90
|
+
n_ctx=args.ctx_size,
|
|
91
|
+
n_batch=args.batch_size,
|
|
92
|
+
n_ubatch=args.ubatch_size,
|
|
93
|
+
n_predict=args.n_predict,
|
|
94
|
+
top_k=args.top_k,
|
|
95
|
+
repeat_last_n=args.repeat_last_n,
|
|
96
|
+
n_gpu_layers=args.n_gpu_layers,
|
|
97
|
+
seed=args.seed,
|
|
98
|
+
temperature=args.temp,
|
|
99
|
+
top_p=args.top_p,
|
|
100
|
+
min_p=args.min_p,
|
|
101
|
+
typical_p=args.typical,
|
|
102
|
+
repeat_penalty=args.repeat_penalty,
|
|
103
|
+
frequency_penalty=args.freq_penalty,
|
|
104
|
+
presence_penalty=args.presence_penalty,
|
|
105
|
+
flash_attn=args.flash_attn,
|
|
106
|
+
verbose=args.verbose,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
print("=========================================================")
|
|
110
|
+
print(f" termux-bitnet CLI (v{__version__})")
|
|
111
|
+
print(f" Model: {resolved_model}")
|
|
112
|
+
print(f" Prompt: {prompt_text[:80]}...")
|
|
113
|
+
print("=========================================================")
|
|
114
|
+
print("[Response]: ", end="", flush=True)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
with BitNetEngine(config) as engine:
|
|
118
|
+
t0 = time.time()
|
|
119
|
+
for token in engine.generate_stream(prompt_text, max_tokens=args.n_predict):
|
|
120
|
+
sys.stdout.write(token)
|
|
121
|
+
sys.stdout.flush()
|
|
122
|
+
t1 = time.time()
|
|
123
|
+
|
|
124
|
+
metrics = engine.get_last_metrics()
|
|
125
|
+
print("\n\n---------------------------------------------------------")
|
|
126
|
+
print(f" Inference Speed: {metrics.tokens_per_second:.2f} tokens/sec")
|
|
127
|
+
print(f" Total Duration: {(t1 - t0)*1000:.1f} ms")
|
|
128
|
+
print("---------------------------------------------------------")
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"\n[termux-bitnet ERROR] Execution failed: {e}", file=sys.stderr)
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cmd_chat(args):
|
|
135
|
+
resolved_model = validate_model_path_or_exit(args.model)
|
|
136
|
+
|
|
137
|
+
config = BitNetConfig(
|
|
138
|
+
model_path=resolved_model,
|
|
139
|
+
system_prompt=args.system_prompt or "",
|
|
140
|
+
stop_tokens=args.stop or "",
|
|
141
|
+
n_threads=args.threads,
|
|
142
|
+
n_ctx=args.ctx_size,
|
|
143
|
+
temperature=args.temp,
|
|
144
|
+
top_p=args.top_p,
|
|
145
|
+
top_k=args.top_k,
|
|
146
|
+
repeat_penalty=args.repeat_penalty,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
print("=========================================================")
|
|
150
|
+
print(f" termux-bitnet Interactive Chat (v{__version__})")
|
|
151
|
+
print(f" Model: {resolved_model}")
|
|
152
|
+
print(" Type 'exit' or 'quit' to end session.")
|
|
153
|
+
print("=========================================================")
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
with BitNetEngine(config) as engine:
|
|
157
|
+
while True:
|
|
158
|
+
try:
|
|
159
|
+
user_input = input("\nUser > ").strip()
|
|
160
|
+
if not user_input:
|
|
161
|
+
continue
|
|
162
|
+
if user_input.lower() in ("exit", "quit", "q"):
|
|
163
|
+
print("Exiting chat session.")
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
print("Assistant > ", end="", flush=True)
|
|
167
|
+
for chunk in engine.generate_stream(user_input, max_tokens=args.n_predict):
|
|
168
|
+
sys.stdout.write(chunk)
|
|
169
|
+
sys.stdout.flush()
|
|
170
|
+
print()
|
|
171
|
+
except KeyboardInterrupt:
|
|
172
|
+
print("\nSession interrupted.")
|
|
173
|
+
break
|
|
174
|
+
except Exception as e:
|
|
175
|
+
print(f"[termux-bitnet ERROR] Chat initialization failed: {e}", file=sys.stderr)
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def cmd_serve(args):
|
|
180
|
+
resolved_model = validate_model_path_or_exit(args.model)
|
|
181
|
+
run_server(host=args.host, port=args.port, model_path=resolved_model)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def cmd_benchmark(args):
|
|
185
|
+
resolved_model = validate_model_path_or_exit(args.model)
|
|
186
|
+
|
|
187
|
+
print("=========================================================")
|
|
188
|
+
print(" termux-bitnet Comprehensive Benchmark ")
|
|
189
|
+
print(f" Model: {resolved_model}")
|
|
190
|
+
print("=========================================================")
|
|
191
|
+
print_hardware_summary()
|
|
192
|
+
|
|
193
|
+
prompts = [
|
|
194
|
+
("Coding (Palindrome)", "Write a Python function to check if a string is palindrome ignoring spaces."),
|
|
195
|
+
("Logic (Harmonic Mean)", "A train goes City A to B at 60 mph, returns at 40 mph. What is average speed?"),
|
|
196
|
+
("Psychology (CBT Analysis)", "Analyze: 'I made a mistake, so I am a total failure and will lose my job.'"),
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
config = BitNetConfig(model_path=resolved_model, n_threads=args.threads)
|
|
200
|
+
try:
|
|
201
|
+
with BitNetEngine(config) as engine:
|
|
202
|
+
for title, p in prompts:
|
|
203
|
+
print(f"\n[Running Benchmark: {title}]")
|
|
204
|
+
t0 = time.time()
|
|
205
|
+
resp = engine.generate(p, max_tokens=100)
|
|
206
|
+
t1 = time.time()
|
|
207
|
+
dur = t1 - t0
|
|
208
|
+
tps = len(resp.split()) / max(dur, 0.001)
|
|
209
|
+
print(f" Result Length: {len(resp)} chars")
|
|
210
|
+
print(f" Elapsed Time: {dur:.2f} sec")
|
|
211
|
+
print(f" Estimated TPS: {tps:.2f} tokens/sec")
|
|
212
|
+
print("\nBenchmark successfully completed.")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
print(f"[termux-bitnet ERROR] Benchmark failed: {e}", file=sys.stderr)
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def main():
|
|
219
|
+
parser = argparse.ArgumentParser(
|
|
220
|
+
prog="termux-bitnet",
|
|
221
|
+
description="High-performance 1.58-bit BitNet Inference SDK & CLI for Android Termux / ARM64."
|
|
222
|
+
)
|
|
223
|
+
parser.add_argument("-v", "--version", action="version", version=f"termux-bitnet {__version__}")
|
|
224
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
225
|
+
|
|
226
|
+
# install
|
|
227
|
+
p_inst = subparsers.add_parser("install", help="1-Click installer and prebuilt engine dispatcher")
|
|
228
|
+
p_inst.set_defaults(func=lambda args: __import__("termux_bitnet.installer", fromlist=["main"]).main())
|
|
229
|
+
|
|
230
|
+
# info
|
|
231
|
+
p_info = subparsers.add_parser("info", help="Inspect local CPU SIMD/DotProd capabilities")
|
|
232
|
+
p_info.set_defaults(func=cmd_info)
|
|
233
|
+
|
|
234
|
+
# download
|
|
235
|
+
p_dl = subparsers.add_parser("download", help="Download 1.58-bit GGUF model")
|
|
236
|
+
p_dl.add_argument("model_name", choices=list(AVAILABLE_MODELS.keys()), default="bitnet-2b", nargs="?", help="Model alias to download")
|
|
237
|
+
p_dl.add_argument("-o", "--output-dir", default=None, help="Target download directory")
|
|
238
|
+
p_dl.add_argument("--force", action="store_true", help="Force re-download even if cached")
|
|
239
|
+
p_dl.set_defaults(func=cmd_download)
|
|
240
|
+
|
|
241
|
+
# run
|
|
242
|
+
p_run = subparsers.add_parser("run", help="Run single prompt inference (Strict Validation)")
|
|
243
|
+
p_run.add_argument("-m", "--model", default=None, help="Path to GGUF model binary (default: auto-discover cached model)")
|
|
244
|
+
p_run.add_argument("-p", "--prompt", default=None, help="Input prompt text")
|
|
245
|
+
p_run.add_argument("-f", "--file", help="Path to prompt text file")
|
|
246
|
+
p_run.add_argument("-d", "--device", default="auto", choices=["auto", "gpu", "vulkan", "cpu"], help="Hardware acceleration backend (default: auto)")
|
|
247
|
+
p_run.add_argument("-t", "--threads", type=int, default=8, help="Worker threads (default: 8)")
|
|
248
|
+
p_run.add_argument("-c", "--ctx-size", type=int, default=2048, help="Context size (default: 2048)")
|
|
249
|
+
p_run.add_argument("-b", "--batch-size", type=int, default=512, help="Batch size (default: 512)")
|
|
250
|
+
p_run.add_argument("-ub", "--ubatch-size", type=int, default=512, help="Micro-batch size (default: 512)")
|
|
251
|
+
p_run.add_argument("-n", "--n-predict", type=int, default=128, help="Max tokens to generate")
|
|
252
|
+
p_run.add_argument("--temp", type=float, default=0.7, help="Softmax temperature (default: 0.7)")
|
|
253
|
+
p_run.add_argument("--top-p", type=float, default=0.95, help="Top-P threshold (default: 0.95)")
|
|
254
|
+
p_run.add_argument("--top-k", type=int, default=40, help="Top-K cutoff (default: 40)")
|
|
255
|
+
p_run.add_argument("--min-p", type=float, default=0.05, help="Min-P cutoff (default: 0.05)")
|
|
256
|
+
p_run.add_argument("--typical", type=float, default=1.0, help="Locally typical sampling (default: 1.0)")
|
|
257
|
+
p_run.add_argument("--repeat-penalty", type=float, default=1.15, help="Repetition penalty (default: 1.15)")
|
|
258
|
+
p_run.add_argument("--repeat-last-n", type=int, default=64, help="Repetition penalty window (default: 64)")
|
|
259
|
+
p_run.add_argument("--freq-penalty", type=float, default=0.0, help="Frequency penalty (default: 0.0)")
|
|
260
|
+
p_run.add_argument("--presence-penalty", type=float, default=0.0, help="Presence penalty (default: 0.0)")
|
|
261
|
+
p_run.add_argument("-s", "--seed", type=int, default=0, help="RNG seed (0 for random)")
|
|
262
|
+
p_run.add_argument("-ngl", "--n-gpu-layers", type=int, default=0, help="GPU/NPU offload layers (default: 0)")
|
|
263
|
+
p_run.add_argument("-fa", "--flash-attn", action="store_true", help="Enable Flash Attention")
|
|
264
|
+
p_run.add_argument("--system-prompt", help="System prompt prefix")
|
|
265
|
+
p_run.add_argument("-r", "--stop", help="Comma-separated stop tokens")
|
|
266
|
+
p_run.add_argument("--verbose", action="store_true", help="Enable verbose logs")
|
|
267
|
+
p_run.set_defaults(func=cmd_run)
|
|
268
|
+
|
|
269
|
+
# chat
|
|
270
|
+
p_chat = subparsers.add_parser("chat", help="Start interactive chat REPL")
|
|
271
|
+
p_chat.add_argument("-m", "--model", default=None, help="Path to GGUF model binary (default: auto-discover cached model)")
|
|
272
|
+
p_chat.add_argument("-d", "--device", default="auto", choices=["auto", "gpu", "vulkan", "cpu"], help="Hardware acceleration backend")
|
|
273
|
+
p_chat.add_argument("-t", "--threads", type=int, default=8, help="Worker threads (default: 8)")
|
|
274
|
+
p_chat.add_argument("-c", "--ctx-size", type=int, default=2048, help="Context size")
|
|
275
|
+
p_chat.add_argument("-n", "--n-predict", type=int, default=256, help="Max tokens per turn")
|
|
276
|
+
p_chat.add_argument("--temp", type=float, default=0.7, help="Temperature")
|
|
277
|
+
p_chat.add_argument("--top-p", type=float, default=0.95, help="Top-P threshold")
|
|
278
|
+
p_chat.add_argument("--top-k", type=int, default=40, help="Top-K cutoff")
|
|
279
|
+
p_chat.add_argument("--repeat-penalty", type=float, default=1.15, help="Repetition penalty")
|
|
280
|
+
p_chat.add_argument("--system-prompt", help="System prompt prefix")
|
|
281
|
+
p_chat.add_argument("-r", "--stop", help="Comma-separated stop tokens")
|
|
282
|
+
p_chat.set_defaults(func=cmd_chat)
|
|
283
|
+
|
|
284
|
+
# serve
|
|
285
|
+
p_serve = subparsers.add_parser("serve", help="Run OpenAI-compatible local API server")
|
|
286
|
+
p_serve.add_argument("-m", "--model", default=None, help="Path to GGUF model binary (default: auto-discover cached model)")
|
|
287
|
+
p_serve.add_argument("--host", default="0.0.0.0", help="Binding host")
|
|
288
|
+
p_serve.add_argument("--port", type=int, default=8080, help="Port number")
|
|
289
|
+
p_serve.set_defaults(func=cmd_serve)
|
|
290
|
+
|
|
291
|
+
# benchmark
|
|
292
|
+
p_bench = subparsers.add_parser("benchmark", help="Run inference performance benchmark")
|
|
293
|
+
p_bench.add_argument("-m", "--model", default=None, help="Path to GGUF model binary (default: auto-discover cached model)")
|
|
294
|
+
p_bench.add_argument("-t", "--threads", type=int, default=4, help="Worker threads")
|
|
295
|
+
p_bench.set_defaults(func=cmd_benchmark)
|
|
296
|
+
|
|
297
|
+
# ── AMEVA Component Protocol v1 ─────────────────────────────────────────
|
|
298
|
+
try:
|
|
299
|
+
from ameva_component.cli_support import build_protocol_subcommands
|
|
300
|
+
build_protocol_subcommands(subparsers)
|
|
301
|
+
_protocol_available = True
|
|
302
|
+
except ImportError:
|
|
303
|
+
_protocol_available = False
|
|
304
|
+
# ────────────────────────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
args = parser.parse_args()
|
|
307
|
+
if hasattr(args, "func"):
|
|
308
|
+
args.func(args)
|
|
309
|
+
elif args.command in ("component", "model", "instance") and _protocol_available:
|
|
310
|
+
from ameva_component.cli_support import dispatch_protocol
|
|
311
|
+
from termux_bitnet.control import BitNetControl
|
|
312
|
+
dispatch_protocol(args, BitNetControl())
|
|
313
|
+
elif args.command in ("component", "model", "instance"):
|
|
314
|
+
import sys
|
|
315
|
+
print("[ERROR] ameva-component-sdk not installed.", file=sys.stderr)
|
|
316
|
+
sys.exit(1)
|
|
317
|
+
else:
|
|
318
|
+
parser.print_help()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
main()
|
|
323
|
+
|
termux_bitnet/config.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Configuration and generation hyperparameters for termux-bitnet."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class BitNetConfig:
|
|
9
|
+
"""Inference hyperparameters and runtime settings."""
|
|
10
|
+
model_path: str = ""
|
|
11
|
+
system_prompt: str = ""
|
|
12
|
+
stop_tokens: str = ""
|
|
13
|
+
device: str = "auto"
|
|
14
|
+
n_threads: int = 4
|
|
15
|
+
n_ctx: int = 2048
|
|
16
|
+
n_batch: int = 512
|
|
17
|
+
n_ubatch: int = 512
|
|
18
|
+
n_predict: int = 128
|
|
19
|
+
top_k: int = 40
|
|
20
|
+
repeat_last_n: int = 64
|
|
21
|
+
n_gpu_layers: int = 0
|
|
22
|
+
seed: int = 0
|
|
23
|
+
temperature: float = 0.7
|
|
24
|
+
top_p: float = 0.95
|
|
25
|
+
min_p: float = 0.05
|
|
26
|
+
typical_p: float = 1.0
|
|
27
|
+
repeat_penalty: float = 1.15
|
|
28
|
+
frequency_penalty: float = 0.0
|
|
29
|
+
presence_penalty: float = 0.0
|
|
30
|
+
flash_attn: bool = False
|
|
31
|
+
verbose: bool = False
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
"""Auto-discover cached model if model_path is not explicitly provided."""
|
|
35
|
+
if not self.model_path or not self.model_path.strip():
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
import os
|
|
38
|
+
cache_dir = Path.home() / ".cache" / "termux-bitnet" / "models"
|
|
39
|
+
if cache_dir.exists():
|
|
40
|
+
for preferred in [
|
|
41
|
+
"bitnet_b1_58-large.Q4_0.gguf",
|
|
42
|
+
"bitnet-large-bitnet_b1_58-large.Q4_0.gguf",
|
|
43
|
+
"bitnet-large.gguf",
|
|
44
|
+
"bitnet-2b-ggml-model-i2_s.gguf",
|
|
45
|
+
]:
|
|
46
|
+
p = cache_dir / preferred
|
|
47
|
+
if p.exists() and p.is_file():
|
|
48
|
+
self.model_path = str(p)
|
|
49
|
+
return
|
|
50
|
+
ggufs = list(cache_dir.glob("*.gguf"))
|
|
51
|
+
if ggufs:
|
|
52
|
+
self.model_path = str(ggufs[0])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class GenerationMetrics:
|
|
57
|
+
"""Performance telemetry of a single generation."""
|
|
58
|
+
prompt_tokens: int = 0
|
|
59
|
+
generated_tokens: int = 0
|
|
60
|
+
prompt_eval_time_ms: float = 0.0
|
|
61
|
+
eval_time_ms: float = 0.0
|
|
62
|
+
tokens_per_second: float = 0.0
|
|
63
|
+
total_time_ms: float = 0.0
|