跑《统计学习导论》(ISLP) 实验时,顺手测了一下常用免费云端算力实例与本地标压笔记本在基础机器学习负载下的性能差异。非严格学术评测,仅记录单机工程数据供参考
一、 测试环境
- OCI ARM:Ampere Altra 架构,4 OCPU (独立物理核),24 GB RAM ,Ubuntu 22.04
- Lightning AI:Free CPU Studio ( 4 vCPU 共享容器)
- Kaggle:标准 CPU 实例( 4 vCPU )及 Tesla T4 GPU 实例
- Local:AMD Ryzen 7 6800H ( 8 核 16 线程)+ RTX 3050 Laptop (4GB VRAM)
<hr>二、 基准测试数据
1. scikit-learn:随机森林分类( CPU )
- 数据规格:合成二分类数据集,40,000 样本,40 特征,200 棵树( 32k 训练 / 8k 测试),random_state=42。全平台测试集准确率均为 95.59%
并行任务数 (n_jobs)OCI ARM (Ampere)Lightning CPUKaggle CPURyzen 7 6800H (本地)134.12 s| 28.34 s
| 70.88 s
| 27.63 s
|| 2 | 17.74 s
| 15.54 s
| 36.97 s
| 14.12 s
|| 4 | 9.50 s
| 11.13 s
| 24.79 s
| 7.43 s
|| 8 | — | — | — | 4.50 s
|| 16 | — | — | — | 3.04 s
|
注:Kaggle CPU 在 4 线程时表现出较明显的扩展瓶颈,耗时为本地 6800H 的 3.3 倍; OCI ARM 在 1 核至 4 核间表现出接近线性的加速比(约 3.59x )
2. PyTorch:CPU 矩阵乘法( 4096×4096 ,float32 )
- 测试逻辑:torch.set_num_interop_threads(1),显式预热,多次采样取中位数耗时
线程数 (threads)OCI ARM (Ampere)Lightning CPUKaggle CPURyzen 7 6800H (本地)13.66 s (37.5 GFLOP/s)| 0.90 s
| 2.03 s
| 1.20 s
|| 2 | 1.85 s (74.3 GFLOP/s)
| 0.48 s
| 1.05 s
| 0.64 s
|| 4 | 1.06 s (129.9 GFLOP/s)
| 0.93 s
| 1.02 s
| 0.44 s
|| 8 | — | — | — | 0.27 s
|| 16 | — | — | — | 0.24 s
|
注:Lightning CPU 在 1~2 线程下受益于 x86 高级矢量扩展(AVX)及底层优化的数学库,单核吞吐占优,但打满 4 线程时收益见顶; OCI ARM 多核扩展曲线相对平稳
3. GPU 单算子基准参照( 4096×4096 Matmul )
- 参数设定:PyTorch float32 ,显式关闭 TF32 ,包含 torch.cuda.synchronize() 同步
硬件平台单次中位数耗时计算吞吐Ryzen 7 6800H (16 线程 CPU)236.80 ms| ~580 GFLOP/s || Kaggle Tesla T4 (16GB VRAM) | 40.86 ms
| ~3363 GFLOP/s || 本地 RTX 3050 Laptop (4GB VRAM) | 36.91 ms
| ~3723 GFLOP/s |
注:纯单算子吞吐下移动端 3050 略胜 T4 ,但 4GB 显存无法承载大规模上下文与 Batch Size ,仅适用于小模型原型快速验证
<hr>三、 观察与局限
- 物理核 vs 容器超卖:Kaggle 与 Lightning 的 4 vCPU 在高并发下扩展性衰减,推测主要受宿主机多租户超卖导致的上下文切换(Context Switch)及 CPU 配额(Quota)节流限制; OCI ARM 绑定的为独立物理核心,扩展曲线受多租户干扰相对较小
- 测试局限:各实例未强制对齐底层线性代数库与依赖环境(如 OpenBLAS / MKL 编译后端差异),随机森林单次采样可能存在瞬态扰动
四、源码
import importlib.metadata as metadataimport osimport platformimport statisticsimport sysimport time
import numpy as npimport sklearnimport torchfrom joblib import cpu_countfrom sklearn.datasets import make_classificationfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import accuracy_scorefrom sklearn.model_selection import train_test_split
ORACLE_RF_SECONDS = {1: 34.124, 2: 17.737, 4: 9.503}ORACLE_TORCH_CPU_SECONDS = {1024: {1: 0.0571, 2: 0.0293, 4: 0.0158},2048: {1: 0.4531, 2: 0.2266, 4: 0.1237},4096: {1: 3.6563, 2: 1.8504, 4: 1.0573},}OFFICIAL_TARGETS = {"numpy": "2.3.5","scipy": "1.16.3","scikit-learn": "1.8.0","ISLP": "0.4.1","torch": "2.10.0",}
def installed_version(name):try:return metadata.version(name)except metadata.PackageNotFoundError:return "未安装"
def environment_report():print("=" * 72)print("环境检查")print("=" * 72)print(f"OS / 架构 : {platform.platform()} / {platform.machine()}")print(f"Python : {sys.version.split()[0]}")print(f"逻辑 CPU ( OS ) : {os.cpu_count()}")print(f"可用 CPU ( joblib ) : {cpu_count()}")print(f"PyTorch : {torch.version}")print(f"PyTorch 默认线程数 : {torch.get_num_threads()}")print(f"CUDA 可用 : {torch.cuda.is_available()}")if torch.cuda.is_available():print(f"GPU : {torch.cuda.get_device_name(0)}")print("\nISLP_labs v2.2.2 官方 requirements 相关目标版本:")for package, target in OFFICIAL_TARGETS.items():actual = installed_version(package)print(f" {package:16} 当前={actual:18} 目标={target}")print("提示:先记录平台预装版本。不要只为版本号相同就替换 Kaggle 预装的 PyTorch ;这可能影响 GPU/CUDA 配置。")print("线程数 1/2/4 用于与 Oracle 的结果横向比较;若平台可用 CPU 少于 4 ,4 线程结果可能受资源限制。")
def random_forest_benchmark():print("\n" + "=" * 72)print("任务 A:scikit-learn 随机森林( CPU )")print("=" * 72)print("数据:40,000 个样本、40 个特征;训练集 32,000 ,测试集 8,000 ; 200 棵树。")data_start = time.perf_counter()X, y = make_classification(n_samples=40_000,n_features=40,n_informative=20,n_redundant=10,n_classes=2,random_state=42,)X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)print(f"数据准备耗时:{time.perf_counter() - data_start:.3f} 秒")results = []for n_jobs in (1, 2, 4):model = RandomForestClassifier(n_estimators=200, max_features="sqrt", random_state=42, n_jobs=n_jobs)t0 = time.perf_counter()model.fit(X_train, y_train)train_seconds = time.perf_counter() - t0t0 = time.perf_counter()pred = model.predict(X_test)predict_seconds = time.perf_counter() - t0accuracy = accuracy_score(y_test, pred)results.append((n_jobs, train_seconds, predict_seconds, accuracy))print(f"\nn_jobs={n_jobs}: 训练 {train_seconds:.3f}s | 预测 {predict_seconds:.3f}s | 准确率 {accuracy:.4%}")baseline = results[0][1]print("\n 与 Oracle 同一任务实测对比(训练时间):")for n_jobs, seconds, predict_seconds, accuracy in results:oracle = ORACLE_RF_SECONDS[n_jobs]if seconds > 0:ratio = oracle / secondslabel = f"Kaggle 约快 {ratio:.2f} 倍" if ratio > 1.01 else (f"Oracle 约快 {1/ratio:.2f} 倍" if ratio < 0.99 else "两者接近")else:label = "无法计算比例"print(f"{n_jobs} 线程 | Kaggle {seconds:.3f}s | Oracle {oracle:.3f}s | {label} | 加速 {baseline/seconds:.2f}x")return results
def pytorch_cpu_benchmark():print("\n" + "=" * 72)print("任务 B:PyTorch CPU 矩阵乘法(与 Oracle 使用相同矩阵规模)")print("=" * 72)print("dtype=float32 ;只测 CPU ;每轮使用中位数,包含一次预热但不计入耗时。")torch.manual_seed(42)torch.set_num_interop_threads(1)all_results = {}for n in (1024, 2048, 4096):print(f"\n 矩阵:{n} × {n}")a = torch.randn(n, n, dtype=torch.float32, device="cpu")b = torch.randn(n, n, dtype=torch.float32, device="cpu")repeats = 5 if n == 1024 else (3 if n == 2048 else 2)all_results[n] = {}for threads in (1, 2, 4):torch.set_num_threads(threads)_ = a @ b # 预热samples = []for _ in range(repeats):t0 = time.perf_counter()c = a @ bsamples.append(time.perf_counter() - t0)del cseconds = statistics.median(samples)gflops = (2 * n**3) / seconds / 1e9all_results[n][threads] = secondsoracle = ORACLE_TORCH_CPU_SECONDS[n][threads]if seconds > 0:ratio = oracle / secondslabel = f"Kaggle 约快 {ratio:.2f} 倍" if ratio > 1.01 else (f"Oracle 约快 {1/ratio:.2f} 倍" if ratio < 0.99 else "两者接近")else:label = "无法计算比例"print(f"threads={threads} | 中位数 {seconds1000:.1f} ms | {gflops:.2f} GFLOP/s | Oracle {oracle1000:.1f} ms | {label}")del a, breturn all_results
def pytorch_gpu_benchmark():print("\n" + "=" * 72)print("任务 C:PyTorch GPU 矩阵乘法(仅当 CUDA 可用时运行)")print("=" * 72)if not torch.cuda.is_available():print("跳过:torch.cuda.is_available() 为 False 。若想测试 Kaggle GPU ,请在 Notebook 的 Settings / Accelerator 中选择 GPU 后重启会话。")returndevice = torch.device("cuda")print(f"GPU:{torch.cuda.get_device_name(0)}")torch.manual_seed(42)for n in (1024, 2048, 4096):a = torch.randn(n, n, dtype=torch.float32, device=device)b = torch.randn(n, n, dtype=torch.float32, device=device)repeats = 10 if n == 1024 else (5 if n == 2048 else 3)_ = a @ btorch.cuda.synchronize()samples = []for _ in range(repeats):torch.cuda.synchronize()t0 = time.perf_counter()c = a @ btorch.cuda.synchronize() # GPU 是异步执行的,计时前后要同步samples.append(time.perf_counter() - t0)del cseconds = statistics.median(samples)gflops = (2 * n**3) / seconds / 1e9print(f"{n} × {n} | GPU 中位数 {seconds*1000:.2f} ms | {gflops:.2f} GFLOP/s")del a, btorch.cuda.empty_cache()
def main():total_start = time.perf_counter()environment_report()random_forest_benchmark()pytorch_cpu_benchmark()pytorch_gpu_benchmark()print("\n" + "=" * 72)print(f"全部测试结束(不含安装依赖):{time.perf_counter() - total_start:.2f} 秒")print("随机森林是 CPU 任务; PyTorch CPU 测试可与 Oracle 的 CPU 数据直接比较。GPU 结果是单独的可选测试。")print("环境、库版本、可用 CPU 和后台负载都可能影响成绩;请将它们作为实测参考,而不是绝对硬件排名。")
if name == "main":main()
⚠️内容来自互联网,如有侵权请点击反馈!侵权投诉 |