bitsandbytes-cpu-fork 0.50.2.dev0__py3-none-win_amd64.whl → 0.50.2.dev1__py3-none-win_amd64.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.
bitsandbytes/__init__.py CHANGED
@@ -75,7 +75,7 @@ __pdoc__ = {
75
75
  "optim.optimizer.MockArgs": False,
76
76
  }
77
77
 
78
- __version__ = "0.50.2.dev0"
78
+ __version__ = "0.50.2.dev1"
79
79
 
80
80
  # 挂载 gdn_cpu(GDN 融合内核 / patch_transformers 入口),让 bnb.gdn_cpu 可直接访问
81
81
  from . import gdn_cpu # noqa: E402,F401
bitsandbytes/cli.py ADDED
@@ -0,0 +1,50 @@
1
+ """Compatibility shim. The reference itself lives in `bitsandbytes_cpu_cli`.
2
+
3
+ Why it is not here: `bitsandbytes/__init__.py` imports torch, and torch is deliberately
4
+ not a dependency of this distribution. So this module can only be imported on a machine
5
+ that already has torch -- which is not the machine whose user needs the help. The console
6
+ script therefore points at the top-level `bitsandbytes_cpu_cli`, which uses nothing but
7
+ the standard library.
8
+
9
+ This file is kept so that `python -m bitsandbytes.cli help` and code that imports the
10
+ tables from here keep working once torch is installed.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import sys
15
+
16
+ from bitsandbytes_cpu_cli import ( # noqa: F401
17
+ EXAMPLES,
18
+ PY,
19
+ SECTIONS,
20
+ SH,
21
+ USAGE,
22
+ WIDTH,
23
+ _row,
24
+ _select,
25
+ _wrap,
26
+ cmd_detect,
27
+ cmd_doctor,
28
+ cmd_help,
29
+ cmd_selftest,
30
+ cmd_version,
31
+ main,
32
+ render_markdown,
33
+ render_text,
34
+ )
35
+
36
+ __all__ = [
37
+ "EXAMPLES",
38
+ "PY",
39
+ "SECTIONS",
40
+ "SH",
41
+ "USAGE",
42
+ "WIDTH",
43
+ "main",
44
+ "render_markdown",
45
+ "render_text",
46
+ ]
47
+
48
+
49
+ if __name__ == "__main__":
50
+ sys.exit(main())
@@ -0,0 +1,189 @@
1
+ # -*- coding: utf-8 -*-
2
+ """detect_cpu.py — 一键检测 CPU 能力 + 推荐配置(开源小白友好)。
3
+
4
+ 自动检测并推荐:
5
+ - CPU 型号 / 物理核 / 逻辑核 / AVX2 / AVX-512
6
+ - 内存总量/可用
7
+ - 推荐线程数(按 GEMM/conv 任务类型)
8
+ - 是否建议用 bnb 8-bit 优化器 / 量化基座 / disk_balancer
9
+ 输出一行可用作配置的推荐摘要,可被训练脚本直接使用。
10
+
11
+ 用法:
12
+ python detect_cpu.py # 打印全部检测 + 推荐
13
+ python detect_cpu.py --json # 输出 JSON(供脚本解析)
14
+ """
15
+ import argparse
16
+ import json
17
+ import os
18
+ import platform
19
+
20
+
21
+ def _cpu_model() -> str:
22
+ """The CPU's name, not the first line of whatever tool we asked.
23
+
24
+ `lscpu` was run and its FIRST line taken, which on every Linux is
25
+ "Architecture: x86_64" -- the report said the CPU was a machine architecture. The
26
+ model name is on the "Model name:" line; /proc/cpuinfo carries it too, and does not
27
+ depend on util-linux being installed.
28
+
29
+ Windows keeps the wmic path but parses the value out rather than the first line,
30
+ since `wmic ... /value` emits "Key=Value" lines whose order is not guaranteed.
31
+ """
32
+ try:
33
+ import subprocess
34
+ if os.name == "nt":
35
+ # Registry first. `wmic` has been removed from current Windows builds, and
36
+ # when it is missing the old code fell through to platform.processor(),
37
+ # which reports "AMD64 Family 23 Model 96 Stepping 1, AuthenticAMD" -- a
38
+ # description of the CPU's family, not its name. The registry key is where
39
+ # the name actually lives and needs no subprocess.
40
+ try:
41
+ import winreg
42
+ k = winreg.OpenKey(
43
+ winreg.HKEY_LOCAL_MACHINE,
44
+ r"HARDWARE\DESCRIPTION\System\CentralProcessor\0")
45
+ return winreg.QueryValueEx(k, "ProcessorNameString")[0].strip()
46
+ except Exception:
47
+ pass
48
+ out = subprocess.run(
49
+ ["wmic", "cpu", "get", "Name", "/value"],
50
+ capture_output=True, text=True, timeout=15).stdout
51
+ for line in out.splitlines():
52
+ if line.strip().lower().startswith("name="):
53
+ return line.split("=", 1)[1].strip()
54
+ else:
55
+ # /proc/cpuinfo first: no dependency on lscpu being present.
56
+ try:
57
+ with open("/proc/cpuinfo", encoding="utf-8", errors="replace") as fh:
58
+ for line in fh:
59
+ if line.lower().startswith("model name"):
60
+ return line.split(":", 1)[1].strip()
61
+ except OSError:
62
+ pass
63
+ out = subprocess.run(["lscpu"], capture_output=True, text=True,
64
+ timeout=15).stdout
65
+ for line in out.splitlines():
66
+ if line.lower().startswith("model name"):
67
+ return line.split(":", 1)[1].strip()
68
+ except Exception:
69
+ pass
70
+ return platform.processor() or "unknown-cpu"
71
+
72
+
73
+ def _physical_cores() -> int:
74
+ """Physical cores, not logical ones: an SMT machine reports twice as many threads
75
+ and the recommendation below depends on the difference.
76
+
77
+ Tries the package-relative import first, because this module now ships inside
78
+ `bitsandbytes`. The bare import is kept for the checkout layout, where the two files
79
+ sit next to each other at the repository root and there is no package to be relative
80
+ to. Falling back to os.cpu_count() reports LOGICAL cores, which on an SMT machine
81
+ makes the recommendation wrong -- so that is the last resort, not the first.
82
+ """
83
+ try:
84
+ from . import torch_cpu_kit as tck
85
+ return tck.physical_cores()
86
+ except Exception:
87
+ pass
88
+ try:
89
+ import torch_cpu_kit as tck
90
+ return tck.physical_cores()
91
+ except Exception:
92
+ return os.cpu_count() or 1
93
+
94
+
95
+ def _mem() -> tuple[float, float]:
96
+ try:
97
+ import psutil
98
+ vm = psutil.virtual_memory()
99
+ return vm.total / 1e9, vm.available / 1e9
100
+ except Exception:
101
+ return 0.0, 0.0
102
+
103
+
104
+ def _cpu_capability() -> str:
105
+ try:
106
+ import torch
107
+ return torch.backends.cpu.get_cpu_capability()
108
+ except Exception:
109
+ return "unknown"
110
+
111
+
112
+ def _recommend_threads(logical: int, physical: int) -> dict:
113
+ """Recommend a thread count per task, from measurements rather than from core count.
114
+
115
+ The previous version returned `logical` for diffusion on any SMT machine, on the
116
+ stated basis that "conv benefits from hyperthreading". Measured on the i5-10400
117
+ (6 physical / 12 logical), that is wrong for the workload this project actually
118
+ runs. The Qwen-Image-2.1 step is 82% dense GEMM, and on the four shapes that model
119
+ uses, six threads beat twelve on every one:
120
+
121
+ 16384x4096x4096 6T 1.316 s 12T 1.449 s
122
+ 16384x4096x12288 6T 4.048 s 12T 4.325 s
123
+ 4096x4096x4096 6T 0.335 s 12T 0.390 s
124
+ GFLOPS 6T 407-418 12T 352-381
125
+
126
+ A whole 1024x1024 render agreed: 225 s/step at 12 threads against 203 at 6. The
127
+ second thread on a physical core shares its FP units and halves its cache, which a
128
+ compute-bound GEMM cannot absorb.
129
+
130
+ So: physical cores everywhere. The `logical` value is still returned under a
131
+ separate key for callers that know their workload is bandwidth- or latency-bound
132
+ rather than arithmetic-bound.
133
+ """
134
+ return {"lm": physical,
135
+ "diffusion": physical,
136
+ "image": physical,
137
+ "logical_if_bandwidth_bound": logical}
138
+
139
+
140
+ def detect() -> dict:
141
+ logical = os.cpu_count() or 1
142
+ physical = _physical_cores()
143
+ total_gb, avail_gb = _mem()
144
+ cap = _cpu_capability()
145
+ r = _recommend_threads(logical, physical)
146
+ return {
147
+ "cpu_model": _cpu_model(),
148
+ "physical_cores": physical,
149
+ "logical_cores": logical,
150
+ "cpu_capability": cap, # 如 AVX2 / AVX512
151
+ "has_avx2": "AVX2" in cap or "AVX512" in cap or "AVXNEON" in cap,
152
+ "has_avx512": "AVX512" in cap,
153
+ "ram_total_gb": round(total_gb, 1),
154
+ "ram_available_gb": round(avail_gb, 1),
155
+ "recommend_threads": r,
156
+ "recommend_bf16": "BF16" in cap, # AVX512-BF16/AMX 才建议 bf16
157
+ "recommend_8bit_optimizer": True, # 纯 CPU 存储紧张时 8bit 优化器省内存
158
+ # 内存读不到时(没装 psutil)不要给 True:avail_gb 是 0,< 8 会成立,
159
+ # 于是这个推荐就变成了一个由缺失依赖制造的假警报。
160
+ "recommend_disk_balancer": bool(avail_gb) and avail_gb < 8,
161
+ }
162
+
163
+
164
+ def main():
165
+ ap = argparse.ArgumentParser()
166
+ ap.add_argument("--json", action="store_true")
167
+ a = ap.parse_args()
168
+ d = detect()
169
+ if a.json:
170
+ print(json.dumps(d, ensure_ascii=False, indent=2))
171
+ return
172
+ print("=== CPU 能力检测 ===")
173
+ print(f" CPU: {d['cpu_model']}")
174
+ print(f" 物理核/逻辑核: {d['physical_cores']}/{d['logical_cores']}")
175
+ print(f" SIMD 能力: {d['cpu_capability']} (AVX2={d['has_avx2']}, AVX-512={d['has_avx512']})")
176
+ print(f" 内存: 总 {d['ram_total_gb']}GB / 可用 {d['ram_available_gb']}GB")
177
+ print("\n=== 推荐配置(实测依据)===")
178
+ t = d["recommend_threads"]
179
+ print(f" LLM 训练线程数 : {t['lm']}(GEMM 密集,超线程只会抢同一个核的 FP 单元)")
180
+ print(f" 生图线程数 : {t['diffusion']}(Qwen-Image 一步 82% 是 GEMM;i5 实测 "
181
+ f"6 线程 203 s/step vs 12 线程 225)")
182
+ print(f" 带宽受限时可用 : {t['logical_if_bandwidth_bound']}(仅当负载不是算术受限)")
183
+ print(f" 推荐 bf16 : {d['recommend_bf16']}(AVX2-only 机器应用 fp32)")
184
+ print(f" 推荐 8bit 优化器: {d['recommend_8bit_optimizer']}(内存紧张时省内存)")
185
+ print(f" 推荐 disk_balancer: {d['recommend_disk_balancer']}(内存<8GB 建议开 --flash)")
186
+
187
+
188
+ if __name__ == "__main__":
189
+ main()