评价此页

大规模部署特性#

创建于:2019年7月24日 | 最后更新于:2020年5月11日

本文档讨论了在大型系统内运行 PyTorch 或在大型组织中操作多个使用 PyTorch 的系统时可能有用的一些扩展点和技巧。

它不涵盖将模型部署到生产环境的主题。请查阅 torch.jit 或相应的教程。

本文档假设您在组织中从源代码构建 PyTorch,或能够静态链接额外的代码以在 PyTorch 使用时加载。因此,许多钩子都以 C++ API 的形式公开,可以在集中位置触发一次,例如在静态初始化代码中。

全集群操作符性能分析#

PyTorch 附带了 torch.autograd.profiler,能够按需测量单个操作符所花费的时间。可以使用相同的机制对任何运行 PyTorch 的进程进行“始终开启”的测量。这对于收集给定进程或整个机器集中运行的 PyTorch 工作负载的信息可能很有用。

可以使用 torch::addGlobalCallback 添加任何操作符调用的新回调。钩子将使用描述调用上下文(例如 name)的 torch::RecordFunction 结构调用。如果启用,RecordFunction::inputs() 包含表示为 torch::IValue 变体类型的函数的参数。请注意,输入日志记录相对昂贵,因此必须明确启用。

操作符回调还可以访问 c10::ThreadLocalDebugInfo::get() 接口,该接口返回指向包含调试信息的结构的指针。此调试信息可以通过使用 at::DebugInfoGuard 对象提前设置。调试信息通过正向(包括异步 fork 任务)和反向传递传播,可用于将有关执行环境(例如模型 ID)的一些额外信息从应用程序的更高层传递到操作符回调。

调用回调会增加一些开销,因此通常只随机抽样操作符调用会很有用。这可以通过传递给 torch::addGlobalCallback 的可选采样率,按每个回调启用。

请注意,addGlobalCallback 不是线程安全的,只能在没有 PyTorch 操作符运行时调用。通常,最好在初始化期间调用它们一次。

这是一个例子

// Called somewhere in the program beginning
void init() {
    // Sample one in a hundred operator runs randomly
    addGlobalCallback(
      RecordFunctionCallback(
        &onFunctionEnter,
        &onFunctionExit)
      .needsInputs(true)
      .samplingProb(0.01)
    );
    // Note, to enable observers in the model calling thread,
    // call enableRecordFunction() in the thread before running a model
}

void onFunctionEnter(const RecordFunction& fn) {
    std::cerr << "Before function " << fn.name()
              << " with " << fn.inputs().size() << " inputs" << std::endl;
}

void onFunctionExit(const RecordFunction& fn) {
    std::cerr << "After function " << fn.name();
}

API 使用日志记录#

在更广泛的生态系统中运行,例如在托管作业调度器中,跟踪哪些二进制文件调用特定的 PyTorch API 通常很有用。在几个重要的 API 点注入了简单的仪器,它会触发给定回调。因为 PyTorch 通常在一次性 Python 脚本中调用,所以对于每个 API,回调仅对给定进程触发一次。

c10::SetAPIUsageHandler 可用于注册 API 使用仪器处理程序。传递的参数将是一个标识使用点的“api key”,例如 PyTorch 扩展导入的 python.import 或触发 TorchScript 编译的 torch.script.compile

SetAPIUsageLogger([](const std::string& event_name) {
    std::cerr << "API was used: " << event_name << std::endl;
});

开发者注意事项:新的 API 触发点可以在代码中通过 C++ 中的 C10_LOG_API_USAGE_ONCE("my_api") 或 Python 中的 torch._C._log_api_usage_once("my.api") 添加。

向保存的 TorchScript 模型附加元数据#

TorchScript 模块可以保存为捆绑序列化参数和 TorchScript 模块代码的存档文件(参见 torch.jit.save())。将附加信息与模型捆绑在一起通常很方便,例如,模型生产者的描述或辅助工件。

这可以通过将 _extra_files 参数传递给 torch.jit.save()torch::jit::load 来实现在保存过程中存储和检索任意二进制大对象。由于 TorchScript 文件是常规 ZIP 存档,因此额外信息作为常规文件存储在存档的 extra/ 目录中。

还有一个全局钩子允许将额外文件附加到当前进程中生成的任何 TorchScript 存档。这对于用生产者元数据标记模型可能很有用,类似于数码相机生成的 JPEG 元数据。示例用法可能如下所示

SetExportModuleExtraFilesHook([](const Module&) {
    ExtraFilesMap files;
    files["producer_info.json"] = "{\"user\": \"" + getenv("USER") + "\"}";
    return files;
});

构建环境考量#

TorchScript 的编译需要访问原始 Python 文件,因为它使用 Python 的 inspect.getsource 调用。在某些生产环境中,这可能需要明确部署 .py 文件以及预编译的 .pyc

常见扩展点#

PyTorch API 通常是松散耦合的,很容易用专门版本替换组件。常见的扩展点包括

  • 用 C++ 实现的自定义操作符 - 有关更多详细信息,请参见教程

  • 自定义数据读取通常可以直接通过调用相应的 Python 库来集成。torch.utils.data 的现有功能可以通过扩展 DatasetIterableDataset 来利用。