Executorch 中的 LLM 简介¶
欢迎来到 LLM 手册!本手册旨在提供一个实际示例以供利用 ExecuTorch 加入您自己的大型语言模型 (LLM)。我们的主要目标是提供 关于如何将我们的系统与您自己的 LLM 集成的清晰简洁的指南。
请注意,此项目仅用于演示,而不是功能齐全 具有最佳性能的示例。因此,某些组件,例如 sampler、tokenizer、 而其他的则以最低版本提供,仅用于演示目的。 因此,模型生成的结果可能会有所不同,并且可能并不总是最佳的。
我们鼓励用户以此项目为起点,并根据自己的特定需求进行调整。 这包括创建您自己的 tokenizer、sampler、acceleration backends 和 其他组件。我们希望这个项目能为您的 LLM 和 ExecuTorch 之旅提供有用的指南。
有关以最佳性能部署 Llama 的信息,请参阅 Llama 指南。
目录¶
先决条件
Hello World 示例
量化
使用 Mobile Acceleration
调试和分析
如何使用自定义内核
如何构建移动应用程序
先决条件¶
要遵循本指南,您需要克隆 ExecuTorch 存储库并安装依赖项。 ExecuTorch 建议使用 Python 3.10 并使用 Conda 来管理您的环境。Conda 不是 必需,但请注意,您可能需要将 python/pip 的使用替换为 python3/pip3 具体取决于您的环境。
有关安装 miniconda 的说明,请参阅此处。
# Create a directory for this example.
mkdir et-nanogpt
cd et-nanogpt
# Clone the ExecuTorch repository and submodules.
mkdir third-party
git clone -b release/0.4 https://github.com/pytorch/executorch.git third-party/executorch
cd third-party/executorch
git submodule update --init
# Create a conda environment and install requirements.
conda create -yn executorch python=3.10.0
conda activate executorch
./install_requirements.sh
cd ../..
有关安装 pyenv-virtualenv 的说明,请参阅此处。
重要的是,如果通过 brew 安装 pyenv,它不会在终端中自动启用 pyenv,从而导致错误。执行以下命令以启用。 请参阅上面的 pyenv-virtualenv 安装指南,了解如何将其添加到 .bashrc 或 .zshrc 中,以避免需要手动运行这些命令。
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
# Create a directory for this example.
mkdir et-nanogpt
cd et-nanogpt
pyenv install -s 3.10
pyenv virtualenv 3.10 executorch
pyenv activate executorch
# Clone the ExecuTorch repository and submodules.
mkdir third-party
git clone -b release/0.4 https://github.com/pytorch/executorch.git third-party/executorch
cd third-party/executorch
git submodule update --init
# Install requirements.
PYTHON_EXECUTABLE=python ./install_requirements.sh
cd ../..
有关更多信息,请参阅设置 ExecuTorch。
在本地运行大型语言模型¶
此示例使用 Karpathy 的 nanoGPT,它是 GPT-2 124M。本指南适用于其他语言模型,因为 ExecuTorch 是模型不变的。
使用 ExecuTorch 运行模型有两个步骤:
导出模型。此步骤会将其预处理为适合运行时执行的格式。
在运行时,加载模型文件并使用 ExecuTorch 运行时运行。
导出步骤提前进行,通常是作为应用程序构建的一部分或在模型更改时进行。生成的 .pte 文件随应用程序一起分发。在运行时,应用程序加载 .pte 文件并将其传递给 ExecuTorch 运行时。
步骤 1.导出到 ExecuTorch¶
导出采用 PyTorch 模型并将其转换为可在使用者设备上高效运行的格式。
在此示例中,您将需要 nanoGPT 模型和相应的分词器词汇表。
curl https://raw.githubusercontent.com/karpathy/nanoGPT/master/model.py -O
curl https://huggingface.co/openai-community/gpt2/resolve/main/vocab.json -O
wget https://raw.githubusercontent.com/karpathy/nanoGPT/master/model.py
wget https://huggingface.co/openai-community/gpt2/resolve/main/vocab.json
要将模型转换为针对独立执行进行优化的格式,有两个步骤。首先,使用 PyTorch 函数将 PyTorch 模型转换为独立于平台的中间表示形式。然后
使用 ExecuTorch 和方法准备模型以在设备上执行。这将创建一个 .pte
文件,该文件可在运行时由桌面或移动应用程序加载。export
to_edge
to_executorch
创建一个名为 export_nanogpt.py 的文件,其中包含以下内容:
# export_nanogpt.py
import torch
from executorch.exir import EdgeCompileConfig, to_edge
from torch.nn.attention import sdpa_kernel, SDPBackend
from torch.export import export, export_for_training
from model import GPT
# Load the model.
model = GPT.from_pretrained('gpt2')
# Create example inputs. This is used in the export process to provide
# hints on the expected shape of the model input.
example_inputs = (torch.randint(0, 100, (1, model.config.block_size), dtype=torch.long), )
# Set up dynamic shape configuration. This allows the sizes of the input tensors
# to differ from the sizes of the tensors in `example_inputs` during runtime, as
# long as they adhere to the rules specified in the dynamic shape configuration.
# Here we set the range of 0th model input's 1st dimension as
# [0, model.config.block_size].
# See https://pytorch.org/executorch/main/concepts.html#dynamic-shapes
# for details about creating dynamic shapes.
dynamic_shape = (
{1: torch.export.Dim("token_dim", max=model.config.block_size)},
)
# Trace the model, converting it to a portable intermediate representation.
# The torch.no_grad() call tells PyTorch to exclude training-specific logic.
with torch.nn.attention.sdpa_kernel([SDPBackend.MATH]), torch.no_grad():
m = export_for_training(model, example_inputs, dynamic_shapes=dynamic_shape).module()
traced_model = export(m, example_inputs, dynamic_shapes=dynamic_shape)
# Convert the model into a runnable ExecuTorch program.
edge_config = EdgeCompileConfig(_check_ir_validity=False)
edge_manager = to_edge(traced_model, compile_config=edge_config)
et_program = edge_manager.to_executorch()
# Save the ExecuTorch program to a file.
with open("nanogpt.pte", "wb") as file:
file.write(et_program.buffer)
要导出,请使用 (或 python3,根据您的环境) 运行脚本。它将在当前目录中生成一个文件。python export_nanogpt.py
nanogpt.pte
有关更多信息,请参阅导出到 ExecuTorch 和 torch.export。
步骤 2。调用 Runtime¶
ExecuTorch 提供了一组运行时 API 和类型来加载和运行模型。
创建一个名为 main.cpp 的文件,其中包含以下内容:
// main.cpp
#include <cstdint>
#include "basic_sampler.h"
#include "basic_tokenizer.h"
#include <executorch/extension/module/module.h>
#include <executorch/extension/tensor/tensor.h>
#include <executorch/runtime/core/evalue.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/core/result.h>
using executorch::aten::ScalarType;
using executorch::aten::Tensor;
using executorch::extension::from_blob;
using executorch::extension::Module;
using executorch::runtime::EValue;
using executorch::runtime::Result;
模型输入和输出采用张量的形式。可以将张量视为多维数组。
ExecuTorch 类提供了张量和其他 ExecuTorch 数据类型的包装器。EValue
由于 LLM 一次生成一个 token,因此驱动程序代码需要重复调用模型,构建 output token 的 Token。每个生成的令牌都作为下次运行的输入传递。
// main.cpp
// The value of the gpt2 `<|endoftext|>` token.
#define ENDOFTEXT_TOKEN 50256
std::string generate(
Module& llm_model,
std::string& prompt,
BasicTokenizer& tokenizer,
BasicSampler& sampler,
size_t max_input_length,
size_t max_output_length) {
// Convert the input text into a list of integers (tokens) that represents it,
// using the string-to-token mapping that the model was trained on. Each token
// is an integer that represents a word or part of a word.
std::vector<int64_t> input_tokens = tokenizer.encode(prompt);
std::vector<int64_t> output_tokens;
for (auto i = 0u; i < max_output_length; i++) {
// Convert the input_tokens from a vector of int64_t to EValue. EValue is a
// unified data type in the ExecuTorch runtime.
auto inputs = from_blob(
input_tokens.data(),
{1, static_cast<int>(input_tokens.size())},
ScalarType::Long);
// Run the model. It will return a tensor of logits (log-probabilities).
auto logits_evalue = llm_model.forward(inputs);
// Convert the output logits from EValue to std::vector, which is what the
// sampler expects.
Tensor logits_tensor = logits_evalue.get()[0].toTensor();
std::vector<float> logits(
logits_tensor.data_ptr<float>(),
logits_tensor.data_ptr<float>() + logits_tensor.numel());
// Sample the next token from the logits.
int64_t next_token = sampler.sample(logits);
// Break if we reached the end of the text.
if (next_token == ENDOFTEXT_TOKEN) {
break;
}
// Add the next token to the output.
output_tokens.push_back(next_token);
std::cout << tokenizer.decode({next_token});
std::cout.flush();
// Update next input.
input_tokens.push_back(next_token);
if (input_tokens.size() > max_input_length) {
input_tokens.erase(input_tokens.begin());
}
}
std::cout << std::endl;
// Convert the output tokens into a human-readable string.
std::string output_string = tokenizer.decode(output_tokens);
return output_string;
}
该类处理加载 .pte 文件并准备执行。Module
分词器负责将提示的人类可读字符串表示形式转换为 模型所需的数字形式。为此,tokenzier 将短子字符串与给定的令牌 ID 相关联。 标记可以被认为是代表单词或单词的一部分,尽管在实践中,它们可能是任意的 字符序列。
分词器从文件加载词汇表,该文件包含每个分词 ID 与文本 ID 之间的映射
代表。调用 和 在字符串和令牌表示形式之间进行转换。tokenizer.encode()
tokenizer.decode()
采样器负责根据 型。LLM 为每个可能的下一个令牌返回一个 logit 值。采样器选择要使用的令牌 在一些策略上。此处使用的最简单方法是获取具有最高 logit 值的令牌。
采样器可以提供可配置的选项,例如可配置的输出选择的随机性, 对重复代币的惩罚,以及优先考虑或不优先考虑特定代币的偏见。
// main.cpp
int main() {
// Set up the prompt. This provides the seed text for the model to elaborate.
std::cout << "Enter model prompt: ";
std::string prompt;
std::getline(std::cin, prompt);
// The tokenizer is used to convert between tokens (used by the model) and
// human-readable strings.
BasicTokenizer tokenizer("vocab.json");
// The sampler is used to sample the next token from the logits.
BasicSampler sampler = BasicSampler();
// Load the exported nanoGPT program, which was generated via the previous
// steps.
Module model("nanogpt.pte", Module::LoadMode::MmapUseMlockIgnoreErrors);
const auto max_input_tokens = 1024;
const auto max_output_tokens = 30;
std::cout << prompt;
generate(
model, prompt, tokenizer, sampler, max_input_tokens, max_output_tokens);
}
最后,将以下文件下载到与 main.cpp 相同的目录中:
curl -O https://raw.githubusercontent.com/pytorch/executorch/main/examples/llm_manual/basic_sampler.h
curl -O https://raw.githubusercontent.com/pytorch/executorch/main/examples/llm_manual/basic_tokenizer.h
要了解更多信息,请参阅 Runtime API 教程。
构建和运行¶
ExecuTorch 使用 CMake 构建系统。要编译和链接 ExecuTorch 运行时,
通过 和 link against 和其他
依赖。add_directory
executorch
创建一个名为 CMakeLists.txt 的文件,其中包含以下内容:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
project(nanogpt_runner)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Set options for executorch build.
option(EXECUTORCH_ENABLE_LOGGING "" ON)
option(EXECUTORCH_BUILD_EXTENSION_DATA_LOADER "" ON)
option(EXECUTORCH_BUILD_EXTENSION_MODULE "" ON)
option(EXECUTORCH_BUILD_EXTENSION_TENSOR "" ON)
option(EXECUTORCH_BUILD_KERNELS_OPTIMIZED "" ON)
# Include the executorch subdirectory.
add_subdirectory(
${CMAKE_CURRENT_SOURCE_DIR}/third-party/executorch
${CMAKE_BINARY_DIR}/executorch
)
add_executable(nanogpt_runner main.cpp)
target_link_libraries(
nanogpt_runner
PRIVATE executorch
extension_module_static # Provides the Module class
extension_tensor # Provides the TensorPtr class
optimized_native_cpu_ops_lib # Provides baseline cross-platform
# kernels
)
此时,工作目录应包含以下文件:
CMakeLists.txt
main.cpp
basic_tokenizer.h
basic_sampler.h
export_nanogpt.py
model.py
vocab.json
nanogpt.pte
如果所有这些都存在,您现在可以构建并运行:
./install_requirements.sh --clean
(mkdir cmake-out && cd cmake-out && cmake ..)
cmake --build cmake-out -j10
./cmake-out/nanogpt_runner
您应该会看到以下消息:
Enter model prompt:
键入模型的一些种子文本,然后按 Enter。这里我们使用 “Hello world!” 作为 示例提示:
Enter model prompt: Hello world!
Hello world!
I'm not sure if you've heard of the "Curse of the Dragon" or not, but it's a very popular game in
此时,它可能会运行得非常缓慢。这是因为 ExecuTorch 没有被告知要针对 特定硬件 (delegation),并且因为它以 32 位浮点 (无量化) 执行所有计算。
代表团¶
虽然 ExecuTorch 为所有 运算符,它还为许多不同的 目标。这些包括但不限于通过 XNNPACK 后端、通过 Core ML 后端的 Apple 加速和 Metal 性能着色器 (MPS) 后端,以及通过 Vulkan 后端实现的 GPU 加速。
由于优化特定于给定的后端,因此每个 pte 文件都是特定的 到以 export 为目标的后端。要支持多个设备,例如 适用于 Android 的 XNNPACK 加速和适用于 iOS 的 Core ML,导出单独的 PTE 文件 对于每个后端。
为了在导出时委托给后端,ExecuTorch 在对象中提供了函数,该函数采用特定于后端的
partitioner 对象。分区程序负责查找
计算图,函数会将匹配的部分委托给给定的后端
加速和优化。计算图的任何部分不是
delegated 将由 ExecuTorch operator 实现执行。to_backend()
EdgeProgramManager
to_backend()
要将导出的模型委托给特定的后端,我们需要将其
partitioner 以及 Edge 编译配置,然后
使用对 created 对象函数的 partitioner 实例进行调用。to_backend
EdgeProgramManager
to_edge
以下是如何将 nanoGPT 委托给 XNNPACK 的示例(例如,如果您要部署到 Android 手机):
# export_nanogpt.py
# Load partitioner for Xnnpack backend
from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner
# Model to be delegated to specific backend should use specific edge compile config
from executorch.backends.xnnpack.utils.configs import get_xnnpack_edge_compile_config
from executorch.exir import EdgeCompileConfig, to_edge
import torch
from torch.export import export
from torch.nn.attention import sdpa_kernel, SDPBackend
from torch.export import export_for_training
from model import GPT
# Load the nanoGPT model.
model = GPT.from_pretrained('gpt2')
# Create example inputs. This is used in the export process to provide
# hints on the expected shape of the model input.
example_inputs = (
torch.randint(0, 100, (1, model.config.block_size - 1), dtype=torch.long),
)
# Set up dynamic shape configuration. This allows the sizes of the input tensors
# to differ from the sizes of the tensors in `example_inputs` during runtime, as
# long as they adhere to the rules specified in the dynamic shape configuration.
# Here we set the range of 0th model input's 1st dimension as
# [0, model.config.block_size].
# See https://pytorch.org/executorch/main/concepts.html#dynamic-shapes
# for details about creating dynamic shapes.
dynamic_shape = (
{1: torch.export.Dim("token_dim", max=model.config.block_size - 1)},
)
# Trace the model, converting it to a portable intermediate representation.
# The torch.no_grad() call tells PyTorch to exclude training-specific logic.
with torch.nn.attention.sdpa_kernel([SDPBackend.MATH]), torch.no_grad():
m = export_for_training(model, example_inputs, dynamic_shapes=dynamic_shape).module()
traced_model = export(m, example_inputs, dynamic_shapes=dynamic_shape)
# Convert the model into a runnable ExecuTorch program.
# To be further lowered to Xnnpack backend, `traced_model` needs xnnpack-specific edge compile config
edge_config = get_xnnpack_edge_compile_config()
edge_manager = to_edge(traced_model, compile_config=edge_config)
# Delegate exported model to Xnnpack backend by invoking `to_backend` function with Xnnpack partitioner.
edge_manager = edge_manager.to_backend(XnnpackPartitioner())
et_program = edge_manager.to_executorch()
# Save the Xnnpack-delegated ExecuTorch program to a file.
with open("nanogpt.pte", "wb") as file:
file.write(et_program.buffer)
此外,更新 CMakeLists.txt 以构建 XNNPACK 后端并将其链接到 ExecuTorch 运行程序。
cmake_minimum_required(VERSION 3.19)
project(nanogpt_runner)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Set options for executorch build.
option(EXECUTORCH_ENABLE_LOGGING "" ON)
option(EXECUTORCH_BUILD_EXTENSION_DATA_LOADER "" ON)
option(EXECUTORCH_BUILD_EXTENSION_MODULE "" ON)
option(EXECUTORCH_BUILD_EXTENSION_TENSOR "" ON)
option(EXECUTORCH_BUILD_KERNELS_OPTIMIZED "" ON)
option(EXECUTORCH_BUILD_XNNPACK "" ON) # Build with Xnnpack backend
# Include the executorch subdirectory.
add_subdirectory(
${CMAKE_CURRENT_SOURCE_DIR}/third-party/executorch
${CMAKE_BINARY_DIR}/executorch
)
add_executable(nanogpt_runner main.cpp)
target_link_libraries(
nanogpt_runner
PRIVATE executorch
extension_module_static # Provides the Module class
extension_tensor # Provides the TensorPtr class
optimized_native_cpu_ops_lib # Provides baseline cross-platform
# kernels
xnnpack_backend # Provides the XNNPACK CPU acceleration backend
)
保持代码的其余部分相同。有关更多详细信息,请参阅导出 添加到 ExecuTorch 并调用 运行时间 了解更多详情
此时,工作目录应包含以下文件:
CMakeLists.txt
main.cpp
basic_tokenizer.h
basic_sampler.h
export_nanogpt.py
model.py
vocab.json
如果所有这些都存在,您现在可以导出 Xnnpack 委托 pte 模型:
python export_nanogpt.py
它将在同一工作目录下生成 。nanogpt.pte
然后,我们可以通过以下方式构建和运行模型:
(rm -rf cmake-out && mkdir cmake-out && cd cmake-out && cmake ..)
cmake --build cmake-out -j10
./cmake-out/nanogpt_runner
您应该会看到以下消息:
Enter model prompt:
键入模型的一些种子文本,然后按 Enter。这里我们使用 “Hello world!” 作为 示例提示:
Enter model prompt: Hello world!
Hello world!
I'm not sure if you've heard of the "Curse of the Dragon" or not, but it's a very popular game in
与非委托模型相比,委托模型应该明显更快。
有关后端委托的更多信息,请参阅 ExecuTorch 指南 对于 XNNPACK 后端,Core ML 后端和 Qualcomm AI Engine Direct 后端。
量化¶
量化是指一组使用较低精度类型运行计算和存储张量的技术。 与 32 位浮点相比,使用 8 位整数可以显著提高和降低 内存使用情况。量化模型的方法有很多种,所需的预处理数据量各不相同 使用的类型,以及对模型准确性和性能的影响。
由于计算和内存在移动设备上受到高度限制,因此需要某种形式的量化才能交付 消费电子产品上的大型模型。特别是,大型语言模型(如 Llama2)可能需要量化 模型权重设置为 4 位或更小。
利用量化需要在导出之前转换模型。PyTorch 提供了 pt2e(PyTorch 2 导出) API 的 API 来实现。此示例使用 XNNPACK 委托以 CPU 加速为目标。因此,它需要使用 特定于 XNNPACK 的量化器。面向不同的后端需要使用相应的量化器。
要将 8 位整数动态量化与 XNNPACK 委托一起使用,请调用 ,通过以下方式校准模型
运行,然后调用 .这将更新要使用的计算图
量化运算符(如果可用)。prepare_pt2e
convert_pt2e
# export_nanogpt.py
from executorch.backends.transforms.duplicate_dynamic_quant_chain import (
DuplicateDynamicQuantChainPass,
)
from torch.ao.quantization.quantizer.xnnpack_quantizer import (
get_symmetric_quantization_config,
XNNPACKQuantizer,
)
from torch.ao.quantization.quantize_pt2e import convert_pt2e, prepare_pt2e
# Use dynamic, per-channel quantization.
xnnpack_quant_config = get_symmetric_quantization_config(
is_per_channel=True, is_dynamic=True
)
xnnpack_quantizer = XNNPACKQuantizer()
xnnpack_quantizer.set_global(xnnpack_quant_config)
m = export_for_training(model, example_inputs).module()
# Annotate the model for quantization. This prepares the model for calibration.
m = prepare_pt2e(m, xnnpack_quantizer)
# Calibrate the model using representative inputs. This allows the quantization
# logic to determine the expected range of values in each tensor.
m(*example_inputs)
# Perform the actual quantization.
m = convert_pt2e(m, fold_quantize=False)
DuplicateDynamicQuantChainPass()(m)
traced_model = export(m, example_inputs)
此外,添加或更新调用以使用 .这会指示 ExecuTorch
通过 XNNPACK 后端优化 CPU 执行模型。to_backend()
XnnpackPartitioner
from executorch.backends.xnnpack.partition.xnnpack_partitioner import (
XnnpackPartitioner,
)
edge_manager = to_edge(traced_model, compile_config=edge_config)
edge_manager = edge_manager.to_backend(XnnpackPartitioner()) # Lower to XNNPACK.
et_program = edge_manager.to_executorch()
最后,确保 Runner 在 CMakeLists.txt 中针对目标进行链接。xnnpack_backend
add_executable(nanogpt_runner main.cpp)
target_link_libraries(
nanogpt_runner
PRIVATE
executorch
extension_module_static # Provides the Module class
optimized_native_cpu_ops_lib # Provides baseline cross-platform kernels
xnnpack_backend) # Provides the XNNPACK CPU acceleration backend
有关更多信息,请参阅 ExecuTorch 中的量化。
分析和调试¶
通过调用 降低模型后,您可能希望查看哪些内容被委托,哪些未被委托。ExecuTorch
提供实用程序方法以提供有关委派的见解。您可以使用此信息来了解
底层计算并诊断潜在的性能问题。模型作者可以使用此信息来
以与目标后端兼容的方式构建模型。to_backend()
可视化委派¶
该方法提供了调用后模型所发生情况的摘要:get_delegation_info()
to_backend()
from executorch.devtools.backend_debug import get_delegation_info
from tabulate import tabulate
# ... After call to to_backend(), but before to_executorch()
graph_module = edge_manager.exported_program().graph_module
delegation_info = get_delegation_info(graph_module)
print(delegation_info.get_summary())
df = delegation_info.get_operator_delegation_dataframe()
print(tabulate(df, headers="keys", tablefmt="fancy_grid"))
对于面向 XNNPACK 后端的 nanoGPT,您可能会看到以下内容(请注意,下面的数字仅用于说明目的,实际值可能会有所不同):
Total delegated subgraphs: 145
Number of delegated nodes: 350
Number of non-delegated nodes: 760
op_type |
# in_delegated_graphs |
# in_non_delegated_graphs |
|
---|---|---|---|
0 |
aten__softmax_default |
12 |
0 |
1 |
aten_add_tensor |
37 |
0 |
2 |
aten_addmm_default |
48 |
0 |
3 |
aten_any_dim |
0 |
12 |
… |
|||
25 |
aten_view_copy_default |
96 |
122 |
… |
|||
30 |
总 |
350 |
760 |
从表中可以看出,该运算符在委托图中出现 96 次,在非委托图中出现 122 次。
要查看更详细的视图,请使用该方法获取整个图形的格式化 str 的打印输出,或者直接用于打印:aten_view_copy_default
format_delegated_graph()
print_delegated_graph()
from executorch.exir.backend.utils import format_delegated_graph
graph_module = edge_manager.exported_program().graph_module
print(format_delegated_graph(graph_module))
这可能会为大型模型生成大量输出。考虑使用“Control+F”或“Command+F”来查找您感兴趣的运算符 (例如“aten_view_copy_default”)。观察哪些实例不在降低的图表下。
在下面的 nanoGPT 输出片段中,观察到 transformer 模块已委托给 XNNPACK,而 where 运算符未委托。
%aten_where_self_22 : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.where.self](args = (%aten_logical_not_default_33, %scalar_tensor_23, %scalar_tensor_22), kwargs = {})
%lowered_module_144 : [num_users=1] = get_attr[target=lowered_module_144]
backend_id: XnnpackBackend
lowered graph():
%p_transformer_h_0_attn_c_attn_weight : [num_users=1] = placeholder[target=p_transformer_h_0_attn_c_attn_weight]
%p_transformer_h_0_attn_c_attn_bias : [num_users=1] = placeholder[target=p_transformer_h_0_attn_c_attn_bias]
%getitem : [num_users=1] = placeholder[target=getitem]
%sym_size : [num_users=2] = placeholder[target=sym_size]
%aten_view_copy_default : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.view_copy.default](args = (%getitem, [%sym_size, 768]), kwargs = {})
%aten_permute_copy_default : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.permute_copy.default](args = (%p_transformer_h_0_attn_c_attn_weight, [1, 0]), kwargs = {})
%aten_addmm_default : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.addmm.default](args = (%p_transformer_h_0_attn_c_attn_bias, %aten_view_copy_default, %aten_permute_copy_default), kwargs = {})
%aten_view_copy_default_1 : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.view_copy.default](args = (%aten_addmm_default, [1, %sym_size, 2304]), kwargs = {})
return [aten_view_copy_default_1]
性能分析¶
通过 ExecuTorch 开发人员工具,用户能够分析模型执行情况,为模型中的每个算子提供时序信息。
先决条件¶
ETRecord 生成(可选)¶
ETRecord 是在导出时生成的工件,其中包含将 ExecuTorch 程序链接到原始 PyTorch 模型的模型图和源级元数据。您可以在没有 ETRecord 的情况下查看所有分析事件,但使用 ETRecord,您还可以将每个事件链接到正在执行的运算符类型、模块层次结构和原始 PyTorch 源代码的堆栈跟踪。有关更多信息,请参阅 ETRecord 文档。
在导出脚本中,在调用 和 后,使用 from 和 from 进行调用。确保复制 ,因为对 的调用会就地更改图形。to_edge()
to_executorch()
generate_etrecord()
EdgeProgramManager
to_edge()
ExecuTorchProgramManager
to_executorch()
EdgeProgramManager
to_backend()
# export_nanogpt.py
import copy
from executorch.devtools import generate_etrecord
# Make the deep copy immediately after to to_edge()
edge_manager_copy = copy.deepcopy(edge_manager)
# ...
# Generate ETRecord right after to_executorch()
etrecord_path = "etrecord.bin"
generate_etrecord(etrecord_path, edge_manager_copy, et_program)
运行导出脚本,ETRecord 将生成为 .etrecord.bin
ETDump 生成¶
ETDump 是在运行时生成的工件,其中包含模型执行的跟踪。有关更多信息,请参阅 ETDump 文档。
在代码中包含 ETDump 标头。
// main.cpp
#include <executorch/devtools/etdump/etdump_flatcc.h>
创建 ETDumpGen 类的 Instance 并将其传递给 Module 构造函数。
std::unique_ptr<ETDumpGen> etdump_gen_ = std::make_unique<ETDumpGen>();
Module model("nanogpt.pte", Module::LoadMode::MmapUseMlockIgnoreErrors, std::move(etdump_gen_));
调用 后,将 ETDump 保存到文件中。您可以捕获多个
model 在单个跟踪中运行(如果需要)。generate()
ETDumpGen* etdump_gen = static_cast<ETDumpGen*>(model.event_tracer());
ET_LOG(Info, "ETDump size: %zu blocks", etdump_gen->get_num_blocks());
etdump_result result = etdump_gen->get_etdump_data();
if (result.buf != nullptr && result.size > 0) {
// On a device with a file system, users can just write it to a file.
FILE* f = fopen("etdump.etdp", "w+");
fwrite((uint8_t*)result.buf, 1, result.size, f);
fclose(f);
free(result.buf);
}
此外,更新CMakeLists.txt以使用 Developer Tools 进行构建,并支持跟踪事件并将其记录到 ETDump 中:
option(EXECUTORCH_ENABLE_EVENT_TRACER "" ON)
option(EXECUTORCH_BUILD_DEVTOOLS "" ON)
# ...
target_link_libraries(
# ... omit existing ones
etdump) # Provides event tracing and logging
target_compile_options(executorch PUBLIC -DET_EVENT_TRACER_ENABLED)
target_compile_options(portable_ops_lib PUBLIC -DET_EVENT_TRACER_ENABLED)
构建并运行运行程序,您将看到生成了一个名为 “etdump.etdp” 的文件。(请注意,这次我们在发布模式下构建以绕过 flatccrt 构建限制。
(rm -rf cmake-out && mkdir cmake-out && cd cmake-out && cmake -DCMAKE_BUILD_TYPE=Release ..)
cmake --build cmake-out -j10
./cmake-out/nanogpt_runner
使用 Inspector API 进行分析¶
收集调试工件 ETDump(以及可选的 ETRecord)后,您可以使用 Inspector API 查看性能信息。
from executorch.devtools import Inspector
inspector = Inspector(etdump_path="etdump.etdp")
# If you also generated an ETRecord, then pass that in as well: `inspector = Inspector(etdump_path="etdump.etdp", etrecord="etrecord.bin")`
with open("inspector_out.txt", "w") as file:
inspector.print_data_tabular(file)
这将以表格格式以 “inspector_out.txt” 格式打印性能数据,每行都是一个分析事件。前几行如下所示: 以完整大小查看
要了解有关 Inspector 及其提供的丰富功能的更多信息,请参阅 Inspector API 参考。
自定义内核¶
借助 ExecuTorch 自定义运算符 API,自定义运算符和内核作者可以轻松地将其内核引入 PyTorch/ExecuTorch。
在 ExecuTorch 中使用自定义内核有三个步骤:
使用 ExecuTorch 类型编写自定义内核。
编译自定义内核并将其链接到 AOT Python 环境和运行时二进制文件。
源到源转换,用于将运算符与自定义运算交换。
编写自定义内核¶
为功能变体(用于 AOT 编译)和 out 变体(用于 ExecuTorch 运行时)定义自定义运算符架构。架构需要遵循 PyTorch ATen 约定(参见 native_functions.yaml)。
custom_linear(Tensor weight, Tensor input, Tensor(?) bias) -> Tensor
custom_linear.out(Tensor weight, Tensor input, Tensor(?) bias, *, Tensor(a!) out) -> Tensor(a!)
根据上面定义的架构编写自定义内核。使用宏使内核可用于 ExecuTorch 运行时。EXECUTORCH_LIBRARY
// custom_linear.h / custom_linear.cpp
#include <executorch/runtime/kernel/kernel_includes.h>
Tensor& custom_linear_out(const Tensor& weight, const Tensor& input, optional<Tensor> bias, Tensor& out) {
// calculation
return out;
}
// Register as myop::custom_linear.out
EXECUTORCH_LIBRARY(myop, "custom_linear.out", custom_linear_out);
要使此运算符在 PyTorch 中可用,您可以围绕 ExecuTorch 自定义内核定义一个包装器。请注意,ExecuTorch implementation 使用 ExecuTorch 张量类型,而 PyTorch 包装器使用 ATen 张量。
// custom_linear_pytorch.cpp
#include "custom_linear.h"
#include <torch/library.h>
at::Tensor custom_linear(const at::Tensor& weight, const at::Tensor& input, std::optional<at::Tensor> bias) {
// initialize out
at::Tensor out = at::empty({weight.size(1), input.size(1)});
// wrap kernel in custom_linear.cpp into ATen kernel
WRAP_TO_ATEN(custom_linear_out, 3)(weight, input, bias, out);
return out;
}
// Register the operator with PyTorch.
TORCH_LIBRARY(myop, m) {
m.def("custom_linear(Tensor weight, Tensor input, Tensor(?) bias) -> Tensor", custom_linear);
m.def("custom_linear.out(Tensor weight, Tensor input, Tensor(?) bias, *, Tensor(a!) out) -> Tensor(a!)", WRAP_TO_ATEN(custom_linear_out, 3));
}
编译并链接自定义内核¶
要使其可用于 ExecuTorch 运行时,请将 custom_linear.h/cpp 编译到二进制目标中。您还可以将内核构建为动态加载的库(.so 或 .dylib)并链接它。
要使其可用于 PyTorch,请将 custom_linear.h、custom_linear.cpp 和 custom_linear_pytorch.cpp 打包到动态加载的库(.so 或 .dylib)中,然后将其加载到 python 环境中。 这是使 PyTorch 在导出时识别自定义运算符所必需的。
import torch
torch.ops.load_library("libcustom_linear.so")
加载后,您可以在 PyTorch 代码中使用自定义运算符。
有关更多信息,请参阅 PyTorch 自定义运算符和 和 ExecuTorch 内核注册。
在模型中使用自定义运算符¶
自定义运算符可以在 PyTorch 模型中显式使用,或者您可以编写转换以将核心运算符的实例替换为自定义变体。对于此示例,您可以找到
的所有实例,并将其替换为 .torch.nn.Linear
CustomLinear
def replace_linear_with_custom_linear(module):
for name, child in module.named_children():
if isinstance(child, nn.Linear):
setattr(
module,
name,
CustomLinear(child.in_features, child.out_features, child.bias),
)
else:
replace_linear_with_custom_linear(child)
其余步骤与正常流程相同。现在,您可以在 Eager 模式下运行此模块,也可以导出到 ExecuTorch。
如何构建移动应用程序¶
请参阅在 iOS 和 Android 上使用 ExecuTorch 构建和运行 LLM 的说明。