CPU 设备¶
CPU 插件是英特尔® 发行版 OpenVINO™ 工具套件的一部分。其开发目的是为了实现英特尔® x86-64 CPU 上神经网络的高性能推理。 有关 CPU 插件的深入描述,请参见:
设备名称¶
CPU 设备名称用于 CPU 插件。即使平台上可以有多个物理套接字,但 OpenVINO™ 也只列出了一个此类设备。
在多套接字平台上,会自动处理 NUMA 节点之间的负载平衡和内存使用分配情况。
为了将 CPU 用于推理,应将设备名称传递到 ov::Core::compile_model() 方法:
ov::Core core;
auto model = core.read_model("model.xml");
auto compiled_model = core.compile_model(model, "CPU");
from openvino.runtime import Core
core = Core()
model = core.read_model("model.xml")
compiled_model = core.compile_model(model, "CPU")
支持的推理数据类型¶
CPU 插件支持以下数据类型作为内部基元的推理精度:
浮点数据类型:
f32bf16
整数数据类型:
i32
量化数据类型:
u8i8u1
Hello 查询设备 C++ 样本 可以用于打印出所有检测到的设备支持的数据类型。
量化数据类型细节¶
每个基元的所选精度取决于 IR 中的操作精度、量化基元和可用的硬件功能。
u1/u8/i8 数据类型仅用于量化操作,即不会自动为非量化操作选择的数据类型。
有关如何获得量化模型的更多详细信息,请参见 低精度优化指南 。
Note
不支持英特尔® AVX512-VNNI 的平台有一个已知的“饱和问题”。该问题可能会导致 u8/i8 精度计算的计算精度降低。
请参见 饱和(溢出)问题部分 以获取有关如何检测此类问题和可能的解决方法的更多信息。
浮点数据类型细节¶
CPU 基元的默认浮点精度为 f32。如需支持 f16 OpenVINO™ IR,插件要在内部将所有 f16 值转换为 f32,并且所有计算都使用 f32 的原生精度执行。
在本地支持 bfloat16 计算的平台上(具有 AVX512_BF16 扩展),会自动使用 bf16 类型,而不会使用 f32,以获得更高性能。因此,运行 bf16 模型不需要采取特殊步骤。
有关 bfloat16 格式的更多详细信息,请参见 BFLOAT16 – 硬件数字定义白皮书 。
使用 bf16 精度提供以下性能优势:
加快两个
bfloat16数字的乘法,因为bfloat16数据的尾数较短。内存消耗减少,因为
bfloat16数据大小是 32 位浮点大小的一半。
如需检查 CPU 设备是否支持 bfloat16 数据类型,请使用 查询设备属性接口 查询应在 CPU 功能列表中包含 BF16 的 ov::device::capabilities 属性:
ov::Core core;
auto cpuOptimizationCapabilities = core.get_property("CPU", ov::device::capabilities);
core = Core()
cpu_optimization_capabilities = core.get_property("CPU", "OPTIMIZATION_CAPABILITIES")
如果模型已转换为 bf16,则 ov::hint::inference_precision 将设置为 ov::element::bf16 并且可以通过 ov::CompiledModel::get_property 调用进行检查。以下代码显示如何获得元件类型:
ov::Core core;
auto network = core.read_model("sample.xml");
auto exec_network = core.compile_model(network, "CPU");
auto inference_precision = exec_network.get_property(ov::hint::inference_precision);
如需在具有原生 bf16 支持的目标上以 f32 精度推理模型,而不是使用 bf16,请将 ov::hint::inference_precision 设置为 ov::element::f32 。
ov::Core core;
core.set_property("CPU", ov::hint::inference_precision(ov::element::f32));
core = Core()
core.set_property("CPU", {"INFERENCE_PRECISION_HINT": "f32"})
Bfloat16 软件模拟模式适用于不支持本机 avx512_bf16 指令、采用英特尔® AVX-512 指令集的 CPU。此模式用于开发目的,无法保证良好的性能。
如需启用模拟,必须明确将 ov::hint::inference_precision 设置为 ov::element::bf16 。
Note
如果在不支持本机 bfloat16 或 bfloat16 模拟模式的 CPU 上将 ov::hint::inference_precision 设置为 ov::element::bf16 ,会引发异常。
Note
由于 bfloat16 数据类型的尾数大小减小。因此生成的 bf16 推理精度可能与 f32 推理不同,特别是对于未使用 bfloat16 数据类型进行训练的模型而言。如果 bf16 推理精度不可接受,建议切换到 f32 精度。
支持的功能¶
多设备执行¶
除 CPU 以外,如果系统包含 OpenVINO™ 支持的设备(例如集成 GPU),则任何支持的模型都可以同时在所有设备上执行。
这可以通过在同时使用 CPU 和 GPU 的情况下将 MULTI:CPU,GPU.0 指定为目标设备来实现。
ov::Core core;
auto model = core.read_model("model.xml");
auto compiled_model = core.compile_model(model, "MULTI:CPU,GPU.0");
core = Core()
model = core.read_model("model.xml")
compiled_model = core.compile_model(model, "MULTI:CPU,GPU.0")
有关更多详细信息,请参见 多设备执行 一文。
多流执行¶
如果为 CPU 插件设置了 ov::num_streams(n_streams) (n_streams > 1) 或 ov::hint::performance_mode(ov::hint::PerformanceMode::THROUGHPUT) 属性,
则可以为模型创建多个流。如果是 CPU 插件,每个流都有自己的主机线程,这意味着可以同时处理传入的推理请求。
就 NUMA 节点的物理内存使用情况而言,每个流都固定到其自己的物理核心组,以最大程度地减少 NUMA 节点之间数据传输的开销。
有关更多详细信息,请参见 优化指南 。
Note
在延迟方面,请注意,在多套接字平台上仅运行一个流可能会在 NUMA 节点之间的数据传输中引入额外开销。 在这种情况下,最好使用 ov::hint::PerformanceMode::LATENCY 性能提示。有关更多详细信息,请参见 性能提示 概述。
动态输入¶
在操作集覆盖范围方面,CPU 为具有动态形状的模型提供完整的功能支持。
Note
CPU 插件不支持动态更改等级的张量。如果尝试使用此种张量推理模型,则会引发异常。
如果提前知道模型形状,则某些运行时优化效果会更好。 因此,如果在推理调用之间不更改输入数据形状,建议使用具有静态形状的模型或使用静态输入形状重塑现有模型,以获得最佳性能。
ov::Core core;
auto model = core.read_model("model.xml");
ov::Shape static_shape = {10, 20, 30, 40};
model->reshape(static_shape);
core = Core()
model = core.read_model("model.xml")
model.reshape([10, 20, 30, 40])
有关更多详细信息,请参见 动态形状指南。
预处理加速¶
CPU 插件支持全套预处理操作,并能高性能实现这些操作。
有关更多详细信息,请参阅 预处理 API 指南 。
支持处理张量精度转换的 CPU 插件仅限用于以下 ov::element 类型:
bf16f16f32f64i8i16i32i64u8u16u32u64boolean
模型缓存¶
CPU 支持导入/导出网络功能。如果通过通用 OpenVINO™ ov::cache_dir 属性启用模型缓存,则插件会在模型编译期间自动在指定目录中创建缓存的 blob。
此缓存的 blob 包含网络的部分表示形式,可执行常见的运行时优化和进行低精度转换。
下次编译模型时,缓存的表示将加载到插件中,而不是初始 OpenVINO™ IR 中。因此将跳过上述转换步骤。
这些转换在模型编译期间会花费大量时间,因此缓存此表示可减少模型后续编译所花费的时间,
从而降低首次推理延迟 (FIL)。
有关更多详细信息,请参见 模型缓存 概述。
扩展性¶
如果 CPU 插件无法实现自己的此类操作,则支持 ov::Op 参考实现的回退。
那意味着 [OpenVINO™ 扩展性机制](@ref openvino_docs_Extensibility_UG_Intro)也可用于插件扩展。
通过重写派生操作类中的 ov::Op::evaluate 方法,可以启用自定义操作实现的回退(请参见[自定义 OpenVINO™ 操作](@ref openvino_docs_Extensibility_UG_add_openvino_ops) 了解详细信息)。
Note
目前,插件不支持具有内部动态的自定义操作(当输出张量形状只能确定作为执行操作的结果时)。
支持的属性¶
插件支持以下属性:
读写属性¶
在调用 ov::Core::compile_model() 之前必须设置所有参数才能生效或作为附加参数传递给 ov::Core::compile_model()
外部依赖包¶
对于某些性能关键深度学习操作,CPU 插件使用 oneAPI Deep Neural Network Library (oneDNN ) 中的优化实现。
使用 OneDNN 库中的基元实现以下操作:
AvgPoolConcatConvolutionConvolutionBackpropDataGroupConvolutionGroupConvolutionBackpropDataGRUCellGRUSequenceLRNLSTMCellLSTMSequenceMatMulMaxPoolRNNCellRNNSequenceSoftMax
优化指南¶
反向规格化数字优化¶
反向规格化数字是非常接近零的非零有限浮点数字,即 (0, 1.17549e-38) 和 (0, -1.17549e-38) 中的数字。在这种情况下,规格化数字编码格式无法对数字进行编码,并且会发生下溢。在很多硬件上,涉及这些数字的计算极其缓慢。
由于反向规格化数字非常接近于零。因此将反向规格化数字直接视为零是优化反向规格化数字计算的一种直接又简单的方法。此优化并不符合 IEEE 754 标准。如果它导致不可接受的精度下降,则可以引入 denormals_optimization 属性来控制此行为。如果用例中存在反向规格化数字,并且未看到精度或可接受的精度下降,则将该属性设置为 True 以提高性能,否则将其设置为 False。如果属性未显式设置优化,并且应用也不执行任何反向规格化数字优化,则默认情况下禁用优化。启用 denormals_optimization 属性后,OpenVINO™ 将提供交叉操作系统/编译器,并在适用时对所有平台进行安全优化。
在某些情况下,使用 OpenVINO™ 的应用也可以执行这种低级别反向规格化数字优化。如果通过在调用 OpenVINO™ 的线程开头,在 MXCSR 寄存器中设置 FTZ (Flush-To-Zero) 和 DAZ (Denormals-As-Zero) 标志进行优化,则 OpenVINO™ 将在同一线程和子线程中继承此设置。因此,无需设置 denormals_optimization 属性。在这种情况下,您负责设置的有效性和安全性。
Note
在调用 compile_model() 之前,必须设置 denormals_optimization 属性。
如需在应用中启用反向规格化数字优化,必须将 denormals_optimization 属性设置为 True:
ov::Core core; // Step 1: create ov::Core object
core.set_property(ov::intel_cpu::denormals_optimization(true)); // Step 1b: Enable denormals optimization
auto model = core.read_model(modelPath); // Step 2: Read Model
//... // Step 3: Prepare inputs/outputs
//... // Step 4: Set device configuration
auto compiled = core.compile_model(model, device, config); // Step 5: LoadNetwork
core = ov.Core()
core.set_property("CPU", ov.properties.intel_cpu.denormals_optimization(True))
model = core.read_model(model=xml_path)
compiled_model = core.compile_model(model=model, device_name=device_name)
稀疏权重解压缩¶
Sparse weights 是其中大多数元素都为零的权重。零元素个数与所有元素个数之比称为 sparse rate。因此,我们假设 sparse weights 是具有高稀疏率的权重。如果遇到 sparse weights,我们可以使用特殊的存储结构,仅在内存中存储非零值,这样就能更高效地使用内存。反过来,这也可以在与高内存密切相关的工作负载(如吞吐量场景)中实现性能提升。
Sparse weights decompression feature 允许在模型编译阶段直接在 CPU 插件中打包矩阵乘法运算的权重,并以特殊的打包格式存储非零值。然后,在模型的执行过程中,这些权重会被解压缩并用于计算内核。由于权重以打包格式从 DDR/L3 高速缓存加载,因此这可以显著减少内存消耗,从而提升推理性能。
为了使用此功能,需要为用户提供 sparse_weights_decompression_rate 属性,该属性可以从区间 [0.5, 1] 取值(当前实现不支持 [0, 0.5] 中的值,请参阅下面的限制)。sparse_weights_decompression_rate 定义稀疏率阈值:只有在稀疏率高于此阈值的情况下,才会使用 sparse weights decompression feature 来执行运算。默认值为 1,表示禁用该选项。
Note
Sparse weights decompression feature 是默认禁用的,因为整体提速在很大程度上取决于特定工作负载,而且在某些情况下,该功能可能会导致性能下降。
sparse_weights_decompression_rate 使用方法代码示例:
ov::Core core; // Step 1: create ov::Core object
core.set_property(ov::intel_cpu::sparse_weights_decompression_rate(0.8)); // Step 1b: Enable sparse weights decompression feature
auto model = core.read_model(modelPath); // Step 2: Read Model
//... // Step 3: Prepare inputs/outputs
//... // Step 4: Set device configuration
auto compiled = core.compile_model(model, device, config); // Step 5: LoadNetwork
core = ov.Core()
core.set_property("CPU", ov.properties.intel_cpu.sparse_weights_decompression_rate(0.8))
model = core.read_model(model=xml_path)
compiled_model = core.compile_model(model=model, device_name=device_name)
Note
在调用 compile_model() 之前,必须设置 sparse_weights_decompression_rate 属性。
有关应用 sparse weights decompression feature 的层的信息可从性能计数器日志中获得。“exec type”(执行类型)字段将包含带有“sparse”(稀疏)粒子的实现类型(下面示例中的“brgemm_avx512_amx_sparse_I8”):
MatMul_1800 EXECUTED layerType: FullyConnected execType: brgemm_avx512_amx_sparse_I8 realTime (ms): 0.050000 cpuTime (ms): 0.050000
限制¶
目前支持 sparse weights decompression feature,但需要遵守以下限制:
模型应该量化到 int8 精度。
仅针对矩阵乘法运算支持功能。
目标硬件必须支持英特尔® AMX 扩展(如第四代英特尔® 至强® 处理器(代号为 Sapphire Rapids))。
权重的输入和输出通道数必须是 64 的倍数。
当前功能实现仅支持高于 0.5 的稀疏率。