跑《统计学习导论》(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)
random_state=42。全平台测试集准确率均为 95.59%并行任务数 (n_jobs) |
OCI ARM (Ampere) | Lightning CPU | Kaggle CPU | Ryzen 7 6800H (本地) |
|---|---|---|---|---|
| 1 | 34.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 )
torch.set_num_interop_threads(1),显式预热,多次采样取中位数耗时线程数 (threads) |
OCI ARM (Ampere) | Lightning CPU | Kaggle CPU | Ryzen 7 6800H (本地) |
|---|---|---|---|---|
| 1 | 3.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 多核扩展曲线相对平稳
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 ,仅适用于小模型原型快速验证
物理核 vs 容器超卖: Kaggle 与 Lightning 的 4 vCPU 在高并发下扩展性衰减,推测主要受宿主机多租户超卖导致的上下文切换(Context Switch)及 CPU 配额(Quota)节流限制; OCI ARM 绑定的为独立物理核心,扩展曲线受多租户干扰相对较小
测试局限: 各实例未强制对齐底层线性代数库与依赖环境(如 OpenBLAS / MKL 编译后端差异),随机森林单次采样可能存在瞬态扰动
import importlib.metadata as metadata import os import platform import statistics import sys import time
import numpy as np import sklearn import torch from joblib import cpu_count from sklearn.datasets import make_classification from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score from 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() - t0 t0 = time.perf_counter() pred = model.predict(X_test) predict_seconds = time.perf_counter() - t0 accuracy = 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 / seconds label = 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 @ b samples.append(time.perf_counter() - t0) del c seconds = statistics.median(samples) gflops = (2 * n**3) / seconds / 1e9 all_results[n][threads] = seconds oracle = ORACLE_TORCH_CPU_SECONDS[n][threads] if seconds > 0: ratio = oracle / seconds label = 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, b return 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 后重启会话。") return device = 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 @ b torch.cuda.synchronize() samples = [] for _ in range(repeats): torch.cuda.synchronize() t0 = time.perf_counter() c = a @ b torch.cuda.synchronize() # GPU 是异步执行的,计时前后要同步 samples.append(time.perf_counter() - t0) del c seconds = statistics.median(samples) gflops = (2 * n**3) / seconds / 1e9 print(f"{n} × {n} | GPU 中位数 {seconds*1000:.2f} ms | {gflops:.2f} GFLOP/s") del a, b torch.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()
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.