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.
@@ -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
+ ]
@@ -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
+
@@ -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
@@ -0,0 +1,3 @@
1
+ """termux_bitnet.control — AMEVA Component Protocol v1."""
2
+ from .component import BitNetControl
3
+ __all__ = ["BitNetControl"]