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 +1 -1
- bitsandbytes/cli.py +50 -0
- bitsandbytes/detect_cpu.py +189 -0
- bitsandbytes/disk_balancer.py +1224 -0
- bitsandbytes/torch_cpu_kit.py +468 -0
- bitsandbytes_cpu_cli.py +949 -0
- {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev1.dist-info}/METADATA +101 -3
- {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev1.dist-info}/RECORD +12 -6
- bitsandbytes_cpu_fork-0.50.2.dev1.dist-info/entry_points.txt +3 -0
- bitsandbytes_cpu_fork-0.50.2.dev1.dist-info/top_level.txt +2 -0
- bitsandbytes_cpu_fork-0.50.2.dev0.dist-info/top_level.txt +0 -1
- {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev1.dist-info}/WHEEL +0 -0
- {bitsandbytes_cpu_fork-0.50.2.dev0.dist-info → bitsandbytes_cpu_fork-0.50.2.dev1.dist-info}/licenses/LICENSE +0 -0
bitsandbytes/__init__.py
CHANGED
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()
|