• 教程 >
  • 制作使用预构建 PyTorch 库的原生 Android 应用程序
快捷方式

制作使用预构建 PyTorch 库的原生 Android 应用程序

作者: Ivan Kobzarev

在本食谱中,您将学习

  • 如何制作一个使用原生代码 (C++) 中的 LibTorch API 的 Android 应用程序。

  • 如何在该应用程序中使用带有自定义运算符的 TorchScript 模型。

您可以在 PyTorch Android 演示应用程序存储库 中找到此应用程序的完整设置。

设置

您将需要一个具有以下已安装包(及其依赖项)的 Python 3 环境

  • PyTorch 1.6

对于 Android 开发,您需要安装

  • Android NDK

wget https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip
unzip android-ndk-r19c-linux-x86_64.zip
export ANDROID_NDK=$(pwd)/android-ndk-r19c
  • Android SDK

wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
unzip sdk-tools-linux-3859397.zip -d android_sdk
export ANDROID_HOME=$(pwd)/android_sdk
  • Gradle 4.10.3

Gradle 是 Android 应用程序最常用的构建系统,我们需要它来构建我们的应用程序。下载它并添加到路径中以便在命令行中使用 gradle

wget https://services.gradle.org/distributions/gradle-4.10.3-bin.zip
unzip gradle-4.10.3-bin.zip
export GRADLE_HOME=$(pwd)/gradle-4.10.3
export PATH="${GRADLE_HOME}/bin/:${PATH}"
  • JDK

Gradle 需要 JDK,您需要安装它并设置环境变量 JAVA_HOME 指向它。例如,您可以按照 说明 安装 OpenJDK。

  • OpenCV SDK for Android

我们自定义的操作符将使用 OpenCV 库实现。要在 Android 上使用它,我们需要下载包含预编译库的 OpenCV SDK for Android。从 OpenCV 发布页面 下载。解压缩并设置环境变量 OPENCV_ANDROID_SDK 指向它。

使用自定义 C++ 操作符准备 TorchScript 模型

TorchScript 允许使用自定义 C++ 操作符,您可以阅读 专门的教程 了解更多信息。

因此,您可以编写使用自定义操作符的模型,该操作符使用 OpenCV 的 cv::warpPerspective 函数。

import torch
import torch.utils.cpp_extension

print(torch.version.__version__)
op_source = """
#include <opencv2/opencv.hpp>
#include <torch/script.h>

torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) {
  cv::Mat image_mat(/*rows=*/image.size(0),
                    /*cols=*/image.size(1),
                    /*type=*/CV_32FC1,
                    /*data=*/image.data_ptr<float>());
  cv::Mat warp_mat(/*rows=*/warp.size(0),
                   /*cols=*/warp.size(1),
                   /*type=*/CV_32FC1,
                   /*data=*/warp.data_ptr<float>());

  cv::Mat output_mat;
  cv::warpPerspective(image_mat, output_mat, warp_mat, /*dsize=*/{64, 64});

  torch::Tensor output =
    torch::from_blob(output_mat.ptr<float>(), /*sizes=*/{64, 64});
  return output.clone();
}

static auto registry =
  torch::RegisterOperators("my_ops::warp_perspective", &warp_perspective);
"""

torch.utils.cpp_extension.load_inline(
    name="warp_perspective",
    cpp_sources=op_source,
    extra_ldflags=["-lopencv_core", "-lopencv_imgproc"],
    is_python_module=False,
    verbose=True,
)

print(torch.ops.my_ops.warp_perspective)


@torch.jit.script
def compute(x, y):
    if bool(x[0][0] == 42):
        z = 5
    else:
        z = 10
    x = torch.ops.my_ops.warp_perspective(x, torch.eye(3))
    return x.matmul(y) + z


compute.save("compute.pt")

此代码片段生成 compute.pt 文件,它是使用自定义操作符 my_ops.warp_perspective 的 TorchScript 模型。

您需要安装用于开发的 OpenCV 才能运行它。对于 Linux 系统,可以使用以下命令:CentOS

yum install opencv-devel

Ubuntu

apt-get install libopencv-dev

创建 Android 应用程序

在成功获得 compute.pt 后,我们希望在 Android 应用程序中使用此 TorchScript 模型。在 Android 上使用通用 TorchScript 模型(没有自定义操作符),使用 Java API,您可以在 这里 找到。我们不能将此方法用于我们的情况,因为我们的模型使用自定义操作符 (my_ops.warp_perspective),默认的 TorchScript 执行将无法找到它。

操作符的注册未公开给 PyTorch Java API,因此我们需要使用原生部分(C++)构建 Android 应用程序,并使用 LibTorch C++ API 为 Android 实现和注册相同的自定义操作符。由于我们的操作符使用 OpenCV 库,我们将使用预编译的 OpenCV Android 库,并使用 OpenCV 中的相同函数。

让我们开始在 NativeApp 文件夹中创建 Android 应用程序。

mkdir NativeApp
cd NativeApp

Android 应用程序构建设置

Android 应用程序构建由主要的 gradle 部分和原生构建 CMake 部分组成。这里的所有代码清单都是完整的文件清单,如果要重新创建整个结构,您将能够构建和安装生成的 Android 应用程序,而无需任何代码添加。

Gradle 构建设置

我们需要添加 gradle 设置文件:build.gradle、gradle.properties、settings.gradle。有关 Android Gradle 构建配置的更多信息,您可以在 这里 找到。

NativeApp/settings.gradle

include ':app'

NativeApp/gradle.properties

android.useAndroidX=true
android.enableJetifier=true

NativeApp/build.gradle

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

NativeApp/build.gradle 中,我们指定 Android gradle 插件版本为 3.5.0。此版本不是最新的。但是,我们使用它,因为 PyTorch android gradle 构建使用此版本。

NativeApp/settings.gradle 显示我们的项目只包含一个模块 - app,它将是我们的 Android 应用程序。

mkdir app
cd app

NativeApp/app/build.gradle

apply plugin: 'com.android.application'

repositories {
  jcenter()
  maven {
    url "https://oss.sonatype.org/content/repositories/snapshots"
  }
}

android {
  configurations {
    extractForNativeBuild
  }
  compileSdkVersion 28
  buildToolsVersion "29.0.2"
  defaultConfig {
    applicationId "org.pytorch.nativeapp"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    externalNativeBuild {
      cmake {
        arguments "-DANDROID_STL=c++_shared"
      }
    }
  }
  buildTypes {
    release {
      minifyEnabled false
    }
  }
  externalNativeBuild {
    cmake {
      path "CMakeLists.txt"
    }
  }
  sourceSets {
    main {
      jniLibs.srcDirs = ['src/main/jniLibs']
    }
  }
}

dependencies {
  implementation 'com.android.support:appcompat-v7:28.0.0'

  implementation 'org.pytorch:pytorch_android:1.6.0-SNAPSHOT'
  extractForNativeBuild 'org.pytorch:pytorch_android:1.6.0-SNAPSHOT'
}

task extractAARForNativeBuild {
  doLast {
    configurations.extractForNativeBuild.files.each {
      def file = it.absoluteFile
      copy {
        from zipTree(file)
        into "$buildDir/$file.name"
        include "headers/**"
        include "jni/**"
      }
    }
  }
}

tasks.whenTaskAdded { task ->
  if (task.name.contains('externalNativeBuild')) {
    task.dependsOn(extractAARForNativeBuild)
  }
}

此 gradle 构建脚本注册了对 pytorch_android 快照的依赖关系,这些快照发布在夜间通道上。

由于它们发布到 nexus sonatype 存储库,因此我们需要注册该存储库:https://oss.sonatype.org/content/repositories/snapshots

在我们的应用程序中,我们需要在应用程序的原生构建部分使用 LibTorch C++ API。为此,我们需要访问预编译的二进制文件和头文件。它们预先打包在 PyTorch Android 构建中,该构建发布在 Maven 存储库中。

要从 gradle 依赖项(即 aar 文件)中使用 PyTorch Android 预编译库,我们应该为配置 extractForNativeBuild 添加注册,在依赖项中添加此配置,并将它的定义放在最后。

extractForNativeBuild 任务将调用 extractAARForNativeBuild 任务,该任务将 pytorch_android aar 解压缩到 gradle 构建目录。

Pytorch_android aar 包含 LibTorch 头文件在 headers 文件夹中,以及针对不同 Android ABI 的预编译库在 jni 文件夹中:$ANDROID_ABI/libpytorch_jni.so$ANDROID_ABI/libfbjni.so。我们将在原生构建中使用它们。

原生构建在此 build.gradle 中用以下行注册

android {
  ...
  externalNativeBuild {
    cmake {
      path "CMakeLists.txt"
    }
}
...
defaultConfig {
  externalNativeBuild {
    cmake {
      arguments "-DANDROID_STL=c++_shared"
    }
  }
}

我们将使用 CMake 配置进行原生构建。这里我们还指定我们将动态链接 STL,因为我们有几个库。有关此内容的更多信息,您可以在 这里 找到。

原生构建 CMake 设置

原生构建将在 NativeApp/app/CMakeLists.txt 中配置

cmake_minimum_required(VERSION 3.4.1)
set(TARGET pytorch_nativeapp)
project(${TARGET} CXX)
set(CMAKE_CXX_STANDARD 14)

set(build_DIR ${CMAKE_SOURCE_DIR}/build)

set(pytorch_testapp_cpp_DIR ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp)
file(GLOB pytorch_testapp_SOURCES
  ${pytorch_testapp_cpp_DIR}/pytorch_nativeapp.cpp
)

add_library(${TARGET} SHARED
    ${pytorch_testapp_SOURCES}
)

file(GLOB PYTORCH_INCLUDE_DIRS "${build_DIR}/pytorch_android*.aar/headers")
file(GLOB PYTORCH_LINK_DIRS "${build_DIR}/pytorch_android*.aar/jni/${ANDROID_ABI}")

target_compile_options(${TARGET} PRIVATE
  -fexceptions
)

set(BUILD_SUBDIR ${ANDROID_ABI})

find_library(PYTORCH_LIBRARY pytorch_jni
  PATHS ${PYTORCH_LINK_DIRS}
  NO_CMAKE_FIND_ROOT_PATH)
find_library(FBJNI_LIBRARY fbjni
  PATHS ${PYTORCH_LINK_DIRS}
  NO_CMAKE_FIND_ROOT_PATH)

# OpenCV
if(NOT DEFINED ENV{OPENCV_ANDROID_SDK})
  message(FATAL_ERROR "Environment var OPENCV_ANDROID_SDK is not set")
endif()

set(OPENCV_INCLUDE_DIR "$ENV{OPENCV_ANDROID_SDK}/sdk/native/jni/include")

target_include_directories(${TARGET} PRIVATE
 "${OPENCV_INCLUDE_DIR}"
  ${PYTORCH_INCLUDE_DIRS})

set(OPENCV_LIB_DIR "$ENV{OPENCV_ANDROID_SDK}/sdk/native/libs/${ANDROID_ABI}")

find_library(OPENCV_LIBRARY opencv_java4
  PATHS ${OPENCV_LIB_DIR}
  NO_CMAKE_FIND_ROOT_PATH)

target_link_libraries(${TARGET}
  ${PYTORCH_LIBRARY}
  ${FBJNI_LIBRARY}
  ${OPENCV_LIBRARY}
  log)

这里我们只注册一个源文件 pytorch_nativeapp.cpp

在之前的步骤中,在 NativeApp/app/build.gradle 中,任务 extractAARForNativeBuild 将头文件和原生库提取到构建目录中。我们将 PYTORCH_INCLUDE_DIRSPYTORCH_LINK_DIRS 设置为它们。

之后,我们找到库 libpytorch_jni.solibfbjni.so,并将它们添加到目标的链接中。

由于我们计划使用 OpenCV 函数来实现我们的自定义操作符 my_ops::warp_perspective - 我们需要链接到 libopencv_java4.so。它打包在 OpenCV SDK for Android 中,该 SDK 在设置步骤中下载。在此配置中,我们通过环境变量 OPENCV_ANDROID_SDK 找到它。

我们还链接 log 库以便能够将我们的结果记录到 Android LogCat。

由于我们链接到 OpenCV Android SDK 的 libopencv_java4.so,我们应该将它复制到 NativeApp/app/src/main/jniLibs/${ANDROID_ABI}

cp -R $OPENCV_ANDROID_SDK/sdk/native/libs/* NativeApp/app/src/main/jniLibs/

将模型文件添加到应用程序

要将 TorschScript 模型 compute.pt 包含在我们的应用程序中,我们应该将它复制到 assets 文件夹

mkdir -p NativeApp/app/src/main/assets
cp compute.pt NativeApp/app/src/main/assets

Android 应用程序清单

每个 Android 应用程序都有一个清单。这里我们指定应用程序名称、包名、主活动。

NativeApp/app/src/main/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.pytorch.nativeapp">

    <application
        android:allowBackup="true"
        android:label="PyTorchNativeApp"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

源代码

Java 代码

现在我们准备在

NativeApp/app/src/main/java/org/pytorch/nativeapp/MainActivity.java:

package org.pytorch.nativeapp;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {

  private static final String TAG = "PyTorchNativeApp";

  public static String assetFilePath(Context context, String assetName) {
    File file = new File(context.getFilesDir(), assetName);
    if (file.exists() && file.length() > 0) {
      return file.getAbsolutePath();
    }

    try (InputStream is = context.getAssets().open(assetName)) {
      try (OutputStream os = new FileOutputStream(file)) {
        byte[] buffer = new byte[4 * 1024];
        int read;
        while ((read = is.read(buffer)) != -1) {
          os.write(buffer, 0, read);
        }
        os.flush();
      }
      return file.getAbsolutePath();
    } catch (IOException e) {
      Log.e(TAG, "Error process asset " + assetName + " to file path");
    }
    return null;
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final String modelFileAbsoluteFilePath =
        new File(assetFilePath(this, "compute.pt")).getAbsolutePath();
    NativeClient.loadAndForwardModel(modelFileAbsoluteFilePath);
  }
}

在之前的步骤中,当我们将 compute.pt 复制到 NativeApp/app/src/main/assets 时,该文件成为 Android 应用程序资产,它将被打包到应用程序中。Android 系统仅提供对其的流访问。要从 LibTorch 使用此模块,我们需要将它物化到磁盘上的一个文件中。 assetFilePath 函数从资产输入流中复制数据,将其写入磁盘,并返回它的绝对文件路径。

OnCreate 方法在活动创建后立即被调用。在此方法中,我们调用 assertFilePath 并调用 NativeClient 类,该类将通过 JNI 调用将其分派到原生代码。

NativeClient 是一个辅助类,它有一个内部私有类 NativePeer,负责处理应用程序的原生部分。它有一个静态块,它将加载 libpytorch_nativeapp.so,该库是用我们之前添加的 CMakeLists.txt 构建的。静态块将在 NativePeer 类的第一次引用时执行。这发生在 NativeClient#loadAndForwardModel 中。

NativeApp/app/src/main/java/org/pytorch/nativeapp/NativeClient.java:

package org.pytorch.nativeapp;

public final class NativeClient {

  public static void loadAndForwardModel(final String modelPath) {
    NativePeer.loadAndForwardModel(modelPath);
  }

  private static class NativePeer {
    static {
      System.loadLibrary("pytorch_nativeapp");
    }

    private static native void loadAndForwardModel(final String modelPath);
  }
}

NativePeer#loadAndForwardModel 声明为 native,它在 Java 中没有定义。对此方法的调用将通过 JNI 重新分派到我们 libpytorch_nativeapp.so 中的 C++ 方法,在 NativeApp/app/src/main/cpp/pytorch_nativeapp.cpp 中。

原生代码

现在我们准备编写应用程序的原生部分。

NativeApp/app/src/main/cpp/pytorch_nativeapp.cpp:

#include <android/log.h>
#include <cassert>
#include <cmath>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#define ALOGI(...)                                                             \
  __android_log_print(ANDROID_LOG_INFO, "PyTorchNativeApp", __VA_ARGS__)
#define ALOGE(...)                                                             \
  __android_log_print(ANDROID_LOG_ERROR, "PyTorchNativeApp", __VA_ARGS__)

#include "jni.h"

#include <opencv2/opencv.hpp>
#include <torch/script.h>

namespace pytorch_nativeapp {
namespace {
torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) {
  cv::Mat image_mat(/*rows=*/image.size(0),
                    /*cols=*/image.size(1),
                    /*type=*/CV_32FC1,
                    /*data=*/image.data_ptr<float>());
  cv::Mat warp_mat(/*rows=*/warp.size(0),
                   /*cols=*/warp.size(1),
                   /*type=*/CV_32FC1,
                   /*data=*/warp.data_ptr<float>());

  cv::Mat output_mat;
  cv::warpPerspective(image_mat, output_mat, warp_mat, /*dsize=*/{8, 8});

  torch::Tensor output =
      torch::from_blob(output_mat.ptr<float>(), /*sizes=*/{8, 8});
  return output.clone();
}

static auto registry =
    torch::RegisterOperators("my_ops::warp_perspective", &warp_perspective);

template <typename T> void log(const char *m, T t) {
  std::ostringstream os;
  os << t << std::endl;
  ALOGI("%s %s", m, os.str().c_str());
}

struct JITCallGuard {
  torch::autograd::AutoGradMode no_autograd_guard{false};
  torch::AutoNonVariableTypeMode non_var_guard{true};
  torch::jit::GraphOptimizerEnabledGuard no_optimizer_guard{false};
};
} // namespace

static void loadAndForwardModel(JNIEnv *env, jclass, jstring jModelPath) {
  const char *modelPath = env->GetStringUTFChars(jModelPath, 0);
  assert(modelPath);
  JITCallGuard guard;
  torch::jit::Module module = torch::jit::load(modelPath);
  module.eval();
  torch::Tensor x = torch::randn({4, 8});
  torch::Tensor y = torch::randn({8, 5});
  log("x:", x);
  log("y:", y);
  c10::IValue t_out = module.forward({x, y});
  log("result:", t_out);
  env->ReleaseStringUTFChars(jModelPath, modelPath);
}
} // namespace pytorch_nativeapp

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
  JNIEnv *env;
  if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;
  }

  jclass c = env->FindClass("org/pytorch/nativeapp/NativeClient$NativePeer");
  if (c == nullptr) {
    return JNI_ERR;
  }

  static const JNINativeMethod methods[] = {
      {"loadAndForwardModel", "(Ljava/lang/String;)V",
       (void *)pytorch_nativeapp::loadAndForwardModel},
  };
  int rc = env->RegisterNatives(c, methods,
                                sizeof(methods) / sizeof(JNINativeMethod));

  if (rc != JNI_OK) {
    return rc;
  }

  return JNI_VERSION_1_6;
}

此代码清单相当长,并且一些东西在这里混合在一起,我们将遵循控制流来了解这段代码是如何工作的。控制流到达的第一个位置是 JNI_OnLoad。此函数在加载库后被调用。它负责注册原生方法,该方法在调用 NativePeer#loadAndForwardModel 时被调用,这里它是 pytorch_nativeapp::loadAndForwardModel 函数。

pytorch_nativeapp::loadAndForwardModel 将模型路径作为参数。首先,我们提取它的 const char* 值,并使用 torch::jit::load 加载模块。

要加载移动设备的 TorchScript 模型,我们需要设置这些保护措施,因为移动设备构建不支持自动梯度等功能,以便实现更小的构建大小,放置在该示例中的 struct JITCallGuard 中。它可能在将来发生变化。您可以通过关注 PyTorch GitHub 中的源代码 来跟踪最新变化。

方法 warp_perspective 的实现和注册完全与 桌面构建的教程 相同。

构建应用程序

要指定 Gradle 找到 Android SDK 和 Android NDK 的位置,我们需要填写 NativeApp/local.properties 文件。

cd NativeApp
echo "sdk.dir=$ANDROID_HOME" >> NativeApp/local.properties
echo "ndk.dir=$ANDROID_NDK" >> NativeApp/local.properties

要构建生成 apk 文件,我们运行

cd NativeApp
gradle app:assembleDebug

要将应用程序安装到已连接的设备上

cd NativeApp
gradle app::installDebug

之后,您可以通过点击 PyTorchNativeApp 图标在设备上运行应用程序。或者,您也可以从命令行进行操作

adb shell am start -n org.pytorch.nativeapp/.MainActivity

如果检查 Android 日志 cat

adb logcat -v brief | grep PyTorchNativeApp

您应该会看到带有标签 'PyTorchNativeApp' 的日志,它会打印 x、y 和模型前向传播的结果,这些结果使用 log 函数在 NativeApp/app/src/main/cpp/pytorch_nativeapp.cpp 中打印。

I/PyTorchNativeApp(26968): x: -0.9484 -1.1757 -0.5832  0.9144  0.8867  1.0933 -0.4004 -0.3389
I/PyTorchNativeApp(26968): -1.0343  1.5200 -0.7625 -1.5724 -1.2073  0.4613  0.2730 -0.6789
I/PyTorchNativeApp(26968): -0.2247 -1.2790  1.0067 -0.9266  0.6034 -0.1941  0.7021 -1.5368
I/PyTorchNativeApp(26968): -0.3803 -0.0188  0.2021 -0.7412 -0.2257  0.5044  0.6592  0.0826
I/PyTorchNativeApp(26968): [ CPUFloatType{4,8} ]
I/PyTorchNativeApp(26968): y: -1.0084  1.8733  0.5435  0.1087 -1.1066
I/PyTorchNativeApp(26968): -1.9926  1.1047  0.5311 -0.4944  1.9178
I/PyTorchNativeApp(26968): -1.5451  0.8867  1.0473 -1.7571  0.3909
I/PyTorchNativeApp(26968):  0.4039  0.5085 -0.2776  0.4080  0.9203
I/PyTorchNativeApp(26968):  0.3655  1.4395 -1.4467 -0.9837  0.3335
I/PyTorchNativeApp(26968): -0.0445  0.8039 -0.2512 -1.3122  0.6543
I/PyTorchNativeApp(26968): -1.5819  0.0525  1.5680 -0.6442 -1.3090
I/PyTorchNativeApp(26968): -1.6197 -0.0773 -0.5967 -0.1105 -0.3122
I/PyTorchNativeApp(26968): [ CPUFloatType{8,5} ]
I/PyTorchNativeApp(26968): result:  16.0274   9.0330   6.0124   9.8644  11.0493
I/PyTorchNativeApp(26968):   8.7633   6.9657  12.3469  10.3159  12.0683
I/PyTorchNativeApp(26968):  12.4529   9.4559  11.7038   7.8396   6.9716
I/PyTorchNativeApp(26968):   8.5279   9.1780  11.3849   8.4368   9.1480
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968): [ CPUFloatType{8,5} ]

您可以在 PyTorch Android 演示应用程序存储库 中找到此应用程序的完整设置。

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获得面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并获得问题的解答

查看资源