生产级机器学习管道的 CPU 利用率更像这样,几乎是 24 / 7 / 365:
检查CPU% 以及该节点上其他资源的状态数据。
我的错误是什么?
先生,阅读您的个人资料是一个令人惊叹的时刻:
I am a computer scientist specializing on algorithms and data analysis by training, and a generalist by nature. My skill set combines a strong scientific background with experience in software architecture and development, especially on solutions for the analysis of big data. I offer consulting and development services and I am looking for challenging projects in the area of data science.
问题是深深地由尊重基本计算机科学+算法规则决定。
问题不是要求强大的科学背景,而是常识。
问题不是任何尤其是大数据,而是需要了解事情的实际运作方式。
事实
还是
情绪? ... 这就是问题所在! (丹麦王子哈姆雷特的悲剧)
我可以说实话吗?让我们更喜欢 FACTS,永远:
第 1 步:
永远不要直接雇佣或解雇每一个不尊重事实的顾问(上面提到的答案没有提出任何建议,承诺的承诺越少)。忽略事实可能是 PR / MARCOM / 广告 / 媒体业务中的“成功之罪”(以防客户容忍此类不诚实和/或操纵习惯),但在一个科学公平的定量领域。 这是不可原谅的。
第 2 步:
永远不要直接雇佣或解雇每一位声称在软件架构方面拥有经验的顾问,尤其是...一旦处理开始分布在某个硬件和软件资源池中,系统架构的每个相应元素将引入的所有附加开销成本。这是不可原谅的。
第 3 步:
永远不要直接雇佣或解雇每一位顾问,一旦事实不符合她/他的意愿,他们就会变得被动攻击,并开始指责其他已经伸出援助之手的知识渊博的人,而不是“改进(他们的) 沟通技巧”,而不是从错误中学习。当然,技巧可能有助于以其他方式表达明显的错误,但是,巨大的错误将仍然是巨大的错误,每个科学家,公平对待她/他的科学头衔,都应该绝不诉诸攻击帮助的同事,而是一个接一个地开始寻找错误的根本原因。这---
@sascha ... May I suggest you take little a break from stackoverflow to cool off, work a little on your interpersonal communication skills
--- 对@sascha 的直接和理智上无法接受的恶意犯规。
接下来是玩具
重要的架构、资源和流程调度事实:
syntax-constructor 的命令式形式会启动大量活动:
joblib.Parallel( n_jobs = <N> )( joblib.delayed( <aFunction> )
( <anOrderedSetOfFunParameters> )
for ( <anOrderedSetOfIteratorParams> )
in <anIterator>
)
为了至少猜测会发生什么,科学上公平的方法是测试几个代表性案例,对其实际执行进行基准测试,收集定量支持的事实,并就行为模型及其对 CPU_core-count 的主要依赖关系提出假设,on RAM 大小,<aFunction>-complexity 和资源分配信封等。
测试用例 A:
def a_NOP_FUN( aNeverConsumedPAR ):
""" __doc__
The intent of this FUN() is indeed to do nothing at all,
so as to be able to benchmark
all the process-instantiation
add-on overhead costs.
"""
pass
##############################################################
### A NAIVE TEST BENCH
##############################################################
from zmq import Stopwatch; aClk = Stopwatch()
JOBS_TO_SPAWN = 4 # TUNE: 1, 2, 4, 5, 10, ..
RUNS_TO_RUN = 10 # TUNE: 10, 20, 50, 100, 200, 500, 1000, ..
try:
aClk.start()
joblib.Parallel( n_jobs = JOBS_TO_SPAWN
)( joblib.delayed( a_NOP_FUN )
( aSoFunPAR )
for ( aSoFunPAR )
in range( RUNS_TO_RUN )
)
except:
pass
finally:
try:
_ = aClk.stop()
except:
_ = -1
pass
print( "CLK:: {0:_>24d} [us] @{1: >3d} run{2: >5d} RUNS".format( _,
JOBS_TO_SPAWN,
RUNS_TO_RUN
)
)
已经在[ RUNS_TO_RUN, JOBS_TO_SPAWN]-cartesian-space DataPoints 的合理缩放的 2D 环境中收集了有关此 NOP 案例的足够代表性数据,以便至少产生一些关于启动实际系统成本的第一手经验本质上为空进程的开销工作负载,与命令式指示的 joblib.Parallel(...)( joblib.delayed(...) )-syntax 构造函数相关,仅在系统调度程序中产生了一些 joblib-managed a_NOP_FUN() 实例。
我们也同意,所有现实世界的问题,包括机器学习模型,都是比刚刚测试的 a_NOP_FUN() 更复杂的工具,而在这两种情况下,您都必须支付已经对间接成本进行了基准测试(即使它是为获得几乎为零的产品而支付的)。
因此,一个科学公平、严谨的工作将从这个最简单的案例开始,已经显示了所有相关设置开销的基准成本有史以来最小的joblib.Parallel()正弦惩罚转发朝着现实世界算法生存的方向 - 最佳,接下来将一些越来越大的“有效负载”-尺寸添加到测试循环中:
测试用例 B:
def a_NOP_FUN_WITH_JUST_A_MEM_ALLOCATOR( aNeverConsumedPAR ):
""" __doc__
The intent of this FUN() is to do nothing but
a MEM-allocation
so as to be able to benchmark
all the process-instantiation
add-on overhead costs.
"""
import numpy as np # yes, deferred import, libs do defer imports
SIZE1D = 1000 # here, feel free to be as keen as needed
aMemALLOC = np.zeros( ( SIZE1D, # so as to set
SIZE1D, # realistic ceilings
SIZE1D, # as how big the "Big Data"
SIZE1D # may indeed grow into
),
dtype = np.float64,
order = 'F'
) # .ALLOC + .SET
aMemALLOC[2,3,4,5] = 8.7654321 # .SET
aMemALLOC[3,3,4,5] = 1.2345678 # .SET
return aMemALLOC[2:3,3,4,5]
再次,
通过在 SIZE1D 的一些合理广泛的范围内运行a_NOP_FUN_WITH_JUST_A_MEM_ALLOCATOR() 来收集有关实际远程进程 MEM 分配成本的具有代表性的定量数据,
再次
在[ RUNS_TO_RUN, JOBS_TO_SPAWN]-cartesian-space DataPoints 的合理缩放的 2D 环境上,以便在joblib.Parallel() 工具内的扩展黑盒 PROCESS_under_TEST 实验下触及性能缩放的新维度,让它的魔力尚未消失-打开。
测试用例 C:
def a_NOP_FUN_WITH_SOME_MEM_DATAFLOW( aNeverConsumedPAR ):
""" __doc__
The intent of this FUN() is to do nothing but
a MEM-allocation plus some Data MOVs
so as to be able to benchmark
all the process-instantiation + MEM OPs
add-on overhead costs.
"""
import numpy as np # yes, deferred import, libs do defer imports
SIZE1D = 1000 # here, feel free to be as keen as needed
aMemALLOC = np.ones( ( SIZE1D, # so as to set
SIZE1D, # realistic ceilings
SIZE1D, # as how big the "Big Data"
SIZE1D # may indeed grow into
),
dtype = np.float64,
order = 'F'
) # .ALLOC + .SET
aMemALLOC[2,3,4,5] = 8.7654321 # .SET
aMemALLOC[3,3,4,5] = 1.2345678 # .SET
aMemALLOC[:,:,:,:]*= 0.1234567
aMemALLOC[:,3,4,:]+= aMemALLOC[4,5,:,:]
aMemALLOC[2,:,4,:]+= aMemALLOC[:,5,6,:]
aMemALLOC[3,3,:,:]+= aMemALLOC[:,:,6,7]
aMemALLOC[:,3,:,5]+= aMemALLOC[4,:,:,7]
return aMemALLOC[2:3,3,4,5]
砰,与架构相关的问题开始慢慢显现:
人们可能很快就会注意到,不仅静态大小很重要,而且 MEM 传输 BANDWIDTH(硬件硬连线)也会开始导致问题,因为将数据从/到 CPU 移动到/来自MEM的成本很高~ 100 .. 300 [ns],比CPU_core“内部”的几个字节的任何智能改组要高得多,{ CPU_core_private | CPU_core_shared | CPU_die_shared }-cache hierarchy-architecture 单独(和任何非本地 NUMA 传输都表现出相同数量级的附加痛苦)。
上述所有测试用例都没有要求 CPU 付出太多努力
所以让我们开始烧油吧!
如果以上所有内容都可以开始闻到引擎盖下的东西实际上是如何工作的,那么这将变得丑陋和肮脏。
测试用例 D:
def a_CPU_1_CORE_BURNER_FUN( aNeverConsumedPAR ):
""" __doc__
The intent of this FUN() is to do nothing but
add some CPU-load
to a MEM-allocation plus some Data MOVs
so as to be able to benchmark
all the process-instantiation + MEM OPs
add-on overhead costs.
"""
import numpy as np # yes, deferred import, libs do defer imports
SIZE1D = 1000 # here, feel free to be as keen as needed
aMemALLOC = np.ones( ( SIZE1D, # so as to set
SIZE1D, # realistic ceilings
SIZE1D, # as how big the "Big Data"
SIZE1D # may indeed grow into
),
dtype = np.float64,
order = 'F'
) # .ALLOC + .SET
aMemALLOC[2,3,4,5] = 8.7654321 # .SET
aMemALLOC[3,3,4,5] = 1.2345678 # .SET
aMemALLOC[:,:,:,:]*= 0.1234567
aMemALLOC[:,3,4,:]+= aMemALLOC[4,5,:,:]
aMemALLOC[2,:,4,:]+= aMemALLOC[:,5,6,:]
aMemALLOC[3,3,:,:]+= aMemALLOC[:,:,6,7]
aMemALLOC[:,3,:,5]+= aMemALLOC[4,:,:,7]
aMemALLOC[:,:,:,:]+= int( [ np.math.factorial( x + aMemALLOC[-1,-1,-1] )
for x in range( 1005 )
][-1]
/ [ np.math.factorial( y + aMemALLOC[ 1, 1, 1] )
for y in range( 1000 )
][-1]
)
return aMemALLOC[2:3,3,4,5]
与机器学习多维空间领域中常见级别的有效载荷相比,仍然没有什么特别之处,其中 { aMlModelSPACE, aSetOfHyperParameterSPACE, aDataSET }-state-space 的所有维度都会影响所需的处理(一些具有O( N ),另一些具有O( N.logN ) 复杂性),其中几乎立即,其中精心设计即使在单个“工作”上,也很快会使用多个CPU_core -正在运行。
一旦天真的(读取资源使用不协调)CPU负载混合开始,并且当与任务相关的CPU负载混合开始与天真的混合(读取资源使用un-coordinated ) O/S-scheduler 进程碰巧在争夺公共资源(仅采用幼稚的共享使用策略)资源 - 即 MEM(将 SWAP 作为 HELL 引入)、CPU(引入缓存未命中和 MEM 重新获取(是的) , 加上 SWAPs 惩罚), 不是说支付任何超过 ~ 15+ [ms] 延迟费用, 如果一个人忘记并让一个过程触摸 fileIO -( 5 (!)-orders-of-magnitude Slow + 共享 + 是纯粹的-[SERIAL],本质上)-设备。这里没有祈祷帮助(包括 SSD,只有几个订单数量级更小,但仍然难以分享和运行设备以令人难以置信的速度进入磨损严重的状态)。
如果所有派生的进程都不适合物理 RAM,会发生什么?
虚拟内存分页和交换开始从字面上恶化到目前为止的其余部分“恰好”-巧合-(阅读:弱协调)-[CONCURRENTLY]-调度处理(阅读:进一步降低了单独的 PROCESS-under-TEST 性能)。
如果没有得到应有的控制和监督,事情可能很快就会遭到破坏。
再一次 - 事实很重要:轻量级资源监视器类可能会有所帮助:
aResRECORDER.show_usage_since0() method returns:
ResCONSUMED[T0+ 166036.311 ( 0.000000)]
user= 2475.15
nice= 0.36
iowait= 0.29
irq= 0.00
softirq= 8.32
stolen_from_VM= 26.95
guest_VM_served= 0.00
类似地,构造更丰富的资源监控器可能会报告更广泛的 O/S 上下文,以查看额外的资源窃取/争用/竞争条件在哪些地方恶化了实际实现的流程:
>>> psutil.Process( os.getpid()
).memory_full_info()
( rss = 9428992,
vms = 158584832,
shared = 3297280,
text = 2322432,
lib = 0,
data = 5877760,
dirty = 0
)
.virtual_memory()
( total = 25111490560,
available = 24661327872,
percent = 1.8,
used = 1569603584,
free = 23541886976,
active = 579739648,
inactive = 588615680,
buffers = 0,
cached = 1119440896
)
.swap_memory()
( total = 8455712768,
used = 967577600,
free = 7488135168,
percent = 11.4,
sin = 500625227776,
sout = 370585448448
)
Wed Oct 19 03:26:06 2017
166.445 ___VMS______________Virtual Memory Size MB
10.406 ___RES____Resident Set Size non-swapped MB
2.215 ___TRS________Code in Text Resident Set MB
14.738 ___DRS________________Data Resident Set MB
3.305 ___SHR_______________Potentially Shared MB
0.000 ___LIB_______________Shared Memory Size MB
__________________Number of dirty pages 0x
最后但并非最不重要的一点是,为什么一个人可以轻松地付出多于收入的回报?
除了逐渐建立的证据记录之外,现实世界的系统部署附加开销如何累积成本,recently re-formulated Amdahl's Law, extended so as to cover both the add-on overhead-costs plus the "process-atomicity" of the further indivisible parts' sizing, defines 最大附加成本阈值,如果某些分布式处理是合理的支付提供上述任何>= 1.00计算进程加速。
不遵守重新制定的阿姆达尔定律的明确逻辑会导致流程进行得比在纯-[SERIAL] 流程调度中处理时更糟糕(有时结果当 joblib.Parallel()( joblib.delayed(...) ) 方法 “阻塞进程” 时,糟糕的设计和/或操作实践可能看起来像是一个案例。