深度學習模型的訓練往往需要大量的計算資源和時間。為了提高訓練效率,我們可以利用多個裝置進行平行計算。JAX 提供了一個名為 pjit() 的函式,可以有效地將張量運算分散到多個裝置上,實作張量平行化。本文將介紹如何使用 pjit() 進行深度學習訓練,並以多層感知器(MLP)為例,展示其應用方法。首先,我們需要準備訓練資料,並使用 TensorFlow Datasets 載入 MNIST 資料集,並進行預處理,例如影像標準化和批次化。接著,我們使用 JAX 定義 MLP 模型,包括初始化引數、定義啟用函式和損失函式等。為了實作平行化,我們需要建立一個裝置網格(Mesh),並使用 jax.sharding.PartitionSpec 指定如何在網格上分割資料和模型引數。最後,我們使用 pjit() 將模型的訓練步驟包裝起來,並在建立的 Mesh 上執行訓練。
使用 pjit() 進行張量平行計算
在這個例子中,我們對張量進行了更多的分片。讓我們仔細看看發生了什麼。首先,我們建立了 4,000 對向量,每個向量有 10,000 個元素。這些向量比之前的要寬得多。然後,我們建立了一個 2 × 4 的裝置陣列,用於 mesh。後來,我們將 mesh 軸命名為「x」和「y」。
兩個輸入引數都分別在第一和第二維度上進行分片。第一維度(大小為 4000)的分片是在「x」軸(大小為 2)上進行的,產生大小為 2000 的塊,這些塊索引向量的子集。第二維度(大小為 10000)的分片是在「y」軸(大小為 4)上進行的,產生大小為 2500 的塊,這些塊索引向量元件的子集。輸出只在單一軸(這裡是「x」)上進行分片,因為它是一個秩為 1 的張量。
計算的過程是這樣的:每個裝置都可以根據它所擁有的分片計算部分的點積。所以,在每個裝置上,第一個陣列的 10,000 個元素的向量的 2,500 個元素的塊與第二個陣列的 10,000 個元素的向量的 2,500 個元素的塊進行元素-wise 乘法,並且這個過程對於裝置上的每 2,000 對向量都進行一次。每個部分的點積產生一個單一的數字,然後我們需要將每個向量的所有部分點積加起來。這是透過隱藏的集體操作實作的。
最後,裝置 mesh 的第一行包含第一 2,000 對向量從兩個陣列的點積。裝置 mesh 的第二行包含第二半部分向量從兩個陣列的點積。最後,我們只需拼接兩個剩餘的分片,就能產生最終結果,包含 4,000 個點積。
現在,將這個方案轉換成程式碼是相當直接的。
列表 D.14:跨 2D mesh 的分片
from jax.sharding import PartitionSpec as P
from jax.sharding import Mesh
import numpy as np
rng_key = random.PRNGKey(42)
vs = random.normal(rng_key, shape=(8_000,10_000))
v1s = vs[:4_000,:]
v2s = vs[4_000:,:]
print(v1s.shape, v2s.shape)
這種 import 方式可以幫助減少打字。
生成 8,000 個「寬」向量,每個向量有 10,000 個元件。
準備一個 2D 陣列的裝置,用於 mesh。
裝置陣列
devices = np.array([
[TpuDevice(id=0, process_index=0, coords=(0,0,0), core_on_chip=0),
TpuDevice(id=1, process_index=0, coords=(0,0,0), core_on_chip=1)],
[TpuDevice(id=2, process_index=0, coords=(1,0,0), core_on_chip=0),
TpuDevice(id=3, process_index=0, coords=(1,0,0), core_on_chip=1)],
[TpuDevice(id=4, process_index=0, coords=(0,1,0), core_on_chip=0),
TpuDevice(id=5, process_index=0, coords=(0,1,0), core_on_chip=1)],
[TpuDevice(id=6, process_index=0, coords=(1,1,0), core_on_chip=0),
TpuDevice(id=7, process_index=0, coords=(1,1,0), core_on_chip=1)]
], dtype=object)
定義點積函式
def dot(v1, v2):
#...
內容解密:
在這個例子中,我們使用 pjit() 進行張量平行計算。首先,我們建立了一個 2D 陣列的裝置,用於 mesh。然後,我們定義了點積函式 dot(),它接受兩個向量 v1 和 v2 作為輸入。函式內部實作了點積計算,並傳回結果。
圖表翻譯:
flowchart TD
A[建立裝置陣列] --> B[定義點積函式]
B --> C[進行點積計算]
C --> D[傳回結果]
這個流程圖展示了點積計算的過程。首先,建立裝置陣列,然後定義點積函式,接著進行點積計算,最後傳回結果。
使用pjit()進行分散式運算
在進行大規模神經網路模型的分散式運算時,pjit()函式可以是一個非常有用的工具。下面,我們將透過一個簡單的多層感知器(MLP)例子來展示如何使用pjit()進行分散式運算。
MLP例子
首先,我們需要載入MNIST資料集。這個資料集包含了許多手寫數字的影像,通常用於測試機器學習模型的效能。
import jax
import jax.numpy as jnp
from jax.experimental import pjit
from jax.experimental import mesh
# 載入MNIST資料集
#...
# 定義dot()函式
def dot(v1, v2):
return jnp.vdot(v1, v2)
# 使用pjit()進行分散式運算
f = pjit(jax.vmap(dot),
in_shardings=(mesh.PartitionSpec('x', 'y'), mesh.PartitionSpec('x', 'y')),
out_shardings=mesh.PartitionSpec('x'))
# 建立Mesh資源管理器
devices = jax.devices()
mesh = mesh.Mesh(devices, ('x', 'y'))
# 執行分散式運算
with mesh:
v1s =... # 輸入向量1
v2s =... # 輸入向量2
x_pjit = f(v1s, v2s)
print(x_pjit.shape)
在上面的例子中,我們使用pjit()函式對dot()函式進行分散式運算。in_shardings引數指定了輸入引數的分割方式,out_shardings引數指定了輸出引數的分割方式。
分析結果
執行上述程式碼後,我們可以看到輸出結果的形狀為(4000,),這表明分散式運算已經成功完成。
(4000,)
檢視生成的HLO程式碼
如果你想檢視生成的HLO程式碼,可以使用以下程式碼:
#...
# 檢視生成的HLO程式碼
hlo_module = f.lower(v1s, v2s).compiler_ir()
print(hlo_module)
這將輸出生成的HLO程式碼,其中包含了自動新增的collective操作。
MLP例子中的pjit()使用
在MLP例子中,我們使用pjit()函式對神經網路模型進行分散式運算。這可以幫助我們加速神經網路模型的訓練和推理過程。
#...
# 定義MLP模型
def mlp(x):
#...
# 使用pjit()進行分散式運算
f = pjit(mlp,
in_shardings=mesh.PartitionSpec('x'),
out_shardings=mesh.PartitionSpec('x'))
# 執行分散式運算
with mesh:
x =... # 輸入資料
y = f(x)
print(y.shape)
在上面的例子中,我們使用pjit()函式對MLP模型進行分散式運算。in_shardings引數指定了輸入引數的分割方式,out_shardings引數指定了輸出引數的分割方式。
使用 pjit() 進行張量平行化
載入資料集
首先,我們需要載入 MNIST 資料集。MNIST 是一個手寫數字的資料集,包含 60,000 個訓練樣本和 10,000 個測試樣本。每個樣本都是 28x28 的灰階影像,代表 0 到 9 之間的一個數字。
import tensorflow as tf
import tensorflow_datasets as tfds
# 定義資料集路徑
data_dir = '/tmp/tfds'
# 載入 MNIST 資料集
data, info = tfds.load(name="mnist",
data_dir=data_dir,
as_supervised=True,
with_info=True)
# 分離訓練和測試資料
data_train = data['train']
data_test = data['test']
定義常數
接下來,我們定義了一些常數,包括影像的高度、寬度、通道數、畫素總數和標籤類別數。
# 影像尺寸
HEIGHT = 28
WIDTH = 28
CHANNELS = 1
# 畫素總數
NUM_PIXELS = HEIGHT * WIDTH * CHANNELS
# 標籤類別數
NUM_LABELS = info.features['label'].num_classes
# 批次大小
BATCH_SIZE = 32
預處理函式
然後,我們定義了一個預處理函式 preprocess,用於將影像和標籤進行預處理。這個函式將影像的畫素值從整數轉換為浮點數,並除以 255.0 進行標準化。
def preprocess(img, label):
"""Resize and preprocess images."""
return (tf.cast(img, tf.float32)/255.0), label
這個預處理函式可以用於將原始影像和標籤轉換為模型可接受的格式。
內容解密:
tfds.load()函式用於載入 TensorFlow 資料集。as_supervised=True引數指定傳回監督式資料,即(input, label)對。with_info=True引數指定傳回額外的後設資料資訊。tf.cast()函式用於將影像的畫素值從整數轉換為浮點數。/255.0用於標準化影像的畫素值,將其對映到 [0, 1] 區間。
圖表翻譯:
以下是使用 Mermaid 圖表語法繪製的預處理流程圖:
graph LR
A[原始影像] -->|tf.cast()|> B[浮點數影像]
B -->|標準化|> C[標準化影像]
C -->|傳回|> D[預處理後影像]
D -->|與標籤配對|> E[預處理後 (input, label) 對]
這個圖表展示了從原始影像到預處理後的 (input, label) 對的流程。
使用TensorFlow和JAX進行神經網路建模
在深度學習中,選擇合適的框架和工具對於模型的效能和開發效率至關重要。這裡,我們將使用TensorFlow和JAX兩個流行的深度學習框架,分別對資料進行預處理和神經網路建模。
資料預處理
首先,讓我們使用TensorFlow來載入和預處理資料。假設我們有訓練資料集data_train和測試資料集data_test,我們可以使用tfds.as_numpy方法將其轉換為NumPy陣列,並進行批次化(batching)和預處理。
import tensorflow as tf
import tensorflow_datasets as tfds
# 載入資料集
data_train, data_test = tfds.load('mnist', split=['train', 'test'])
# 定義預處理函式
def preprocess(ex):
image = ex['image']
label = ex['label']
return image, label
# 對資料集進行預處理、批次化和預先載入
train_data = tfds.as_numpy(data_train.map(preprocess).batch(32).prefetch(1))
test_data = tfds.as_numpy(data_test.map(preprocess).batch(32).prefetch(1))
神經網路建模
接下來,讓我們使用JAX來定義一個多層感知器(MLP)模型。JAX是一個根據NumPy的高效能深度學習框架,提供了強大的自動微分和向量化功能。
import jax
import jax.numpy as jnp
from jax import grad, jit, vmap, value_and_grad
from jax.nn import swish, logsumexp, one_hot
# 定義模型引數
LAYER_SIZES = [28*28, 512, 10]
PARAM_SCALE = 0.01
# 初始化模型引數
params = []
for i in range(len(LAYER_SIZES) - 1):
params.append(jax.random.normal(jax.random.PRNGKey(0), (LAYER_SIZES[i], LAYER_SIZES[i+1]), dtype=jnp.float32) * PARAM_SCALE)
模型訓練
有了資料和模型之後,我們就可以開始訓練模型了。這裡,我們使用JAX的grad函式來計算損失函式的梯度,並使用jit函式來編譯模型以提高效能。
# 定義損失函式
def loss(params, inputs, labels):
# 前向傳播
outputs = inputs
for i in range(len(params)):
outputs = swish(jnp.dot(outputs, params[i]))
# 計算損失
loss = -jnp.mean(logsumexp(outputs - jnp.max(outputs, axis=1, keepdims=True)))
return loss
# 訓練模型
for epoch in range(10):
for batch in train_data:
inputs, labels = batch
# 計算梯度
grads = grad(loss)(params, inputs, labels)
# 更新模型引數
params = [param - 0.01 * grad for param, grad in zip(params, grads)]
這裡,我們簡要介紹瞭如何使用TensorFlow和JAX進行神經網路建模和訓練。實際上,還有很多細節需要注意,例如模型的初始化、最佳化器的選擇、過度擬合的防止等等。
神經網路引數初始化與預測
在構建神經網路時,初始化引數是一個非常重要的步驟。這涉及到為每一層的權重和偏差分配初始值。下面,我們將探討如何實作這一過程,並結合實際的預測功能。
初始化神經網路引數
首先,我們需要定義一個函式來初始化神經網路的引數。這個函式應該根據給定的層大小、隨機種子和縮放因子來生成權重和偏差。
import jax
import jax.numpy as jnp
from jax import random
def init_network_params(sizes, key=random.PRNGKey(0), scale=1e-2):
"""
初始化所有層的引數為一個全連線神經網路。
引數:
- sizes: 列表,包含每一層的神經元數量。
- key: 隨機種子,用於生成隨機權重和偏差。
- scale: 縮放因子,用於調整初始權重和偏差的大小。
傳回:
- 一個列表,包含每一層的權重和偏差。
"""
keys = random.split(key, len(sizes) - 1)
return [random_layer_params(m, n, k, scale) for m, n, k in zip(sizes[:-1], sizes[1:], keys)]
def random_layer_params(m, n, key, scale=1e-2):
"""
一個幫助函式,用於隨機初始化權重和偏差。
引數:
- m: 輸入特徵數量。
- n: 輸出特徵數量。
- key: 隨機種子。
- scale: 縮放因子。
傳回:
- 一個元組,包含權重和偏差。
"""
w_key, b_key = random.split(key)
return scale * random.normal(w_key, (n, m)), scale * random.normal(b_key, (n,))
預測函式
接下來,我們需要定義一個預測函式,該函式可以根據給定的神經網路引數和輸入影像進行預測。
def predict(params, image):
"""
進行單個例子的預測。
引數:
- params: 神經網路引數,包括權重和偏差。
- image: 輸入影像。
傳回:
- 預測結果。
"""
activations = image
for w, b in params[:-1]:
outputs = jnp.dot(activations, w) + b
activations = jnp.relu(outputs) # 使用ReLU啟用函式
# 最後一層使用線性啟用函式
final_outputs = jnp.dot(activations, params[-1][0]) + params[-1][1]
return final_outputs
初始化引數與預測
最後,我們可以使用上述函式來初始化神經網路引數和進行預測。
LAYER_SIZES = [784, 256, 128, 10] # 定義層大小
PARAM_SCALE = 1e-2 # 定義縮放因子
init_params = init_network_params(LAYER_SIZES, random.PRNGKey(0), scale=PARAM_SCALE)
# 假設我們有一個輸入影像
image = jnp.array([...]) # 對影像進行預處理
# 進行預測
prediction = predict(init_params, image)
這樣,我們就完成了神經網路引數的初始化和預測功能的實作。這些函式可以根據給定的層大小、隨機種子和縮放因子來生成初始引數,並使用這些引數對輸入影像進行預測。
使用 pjit 進行分散式訓練的損失和更新函式
在進行分散式訓練時,我們需要調整損失和更新函式,以便能夠使用 pjit 進行平行計算。以下是調整後的損失和更新函式:
損失函式
def loss(params, images, targets):
"""
分類別交叉熵損失函式。
"""
logits = batched_predict(params, images)
log_preds = logits - jnp.log(jnp.sum(jnp.exp(logits), axis=1, keepdims=True))
return -jnp.mean(targets * log_preds)
更新函式
def update(params, x, y, epoch_number):
# 定義學習率
lr = INIT_LR * (DECAY_RATE ** (epoch_number // DECAY_STEPS))
# 計算梯度
grads = jax.grad(loss)(params, x, y)
# 更新引數
new_params = [param - lr * grad for param, grad in zip(params, grads)]
return new_params
使用 pjit 進行分散式訓練
# 定義 Mesh 和 PartitionSpec
mesh = Mesh(np.array([4, 4]), ('data', 'model'))
in_axis = P('data', 'model')
out_axis = P('data', 'model')
# 使用 pjit 進行分散式訓練
@pjit(in_axis, out_axis, mesh=mesh)
def batched_predict_pjit(params, images):
return batched_predict(params, images)
@pjit(in_axis, out_axis, mesh=mesh)
def loss_pjit(params, images, targets):
return loss(params, images, targets)
@pjit(in_axis, out_axis, mesh=mesh)
def update_pjit(params, x, y, epoch_number):
return update(params, x, y, epoch_number)
圖表翻譯
flowchart TD
A[初始化引數] --> B[計算梯度]
B --> C[更新引數]
C --> D[計算損失]
D --> E[傳回損失值]
圖表說明
上述圖表展示了使用 pjit 進行分散式訓練的流程。首先,初始化引數,然後計算梯度,接著更新引數,然後計算損失,最後傳回損失值。
內容解密
在上述程式碼中,我們定義了損失函式和更新函式,並使用 pjit 進行分散式訓練。損失函式計算了分類別交叉熵損失,而更新函式則更新了引數以最小化損失。使用 pjit 進行分散式訓練可以大大提高計算效率。
使用 pjit() 進行張量平行化
在進行深度學習訓練時,能夠有效地利用多個裝置來加速計算是非常重要的。JAX 提供了一個名為 pjit() 的函式,可以用來對模型進行平行化處理。下面,我們將探討如何使用 pjit() 來實作張量平行化。
初始化和準備
首先,我們需要初始化模型的引數和輸入資料。這包括定義模型的架構、損失函式、最佳化器等。
import jax
import jax.numpy as jnp
# 定義模型引數
params =...
# 定義輸入資料
x =...
y =...
# 定義損失函式
def loss(params, x, y):
# 計算預測結果
predicted = batched_predict(params, x)
# 計算損失
loss_value = jnp.mean((predicted - y) ** 2)
return loss_value
# 定義最佳化器
def update(params, x, y):
# 計算梯度
grads = jax.grad(loss)(params, x, y)
# 更新引數
params = [w - 0.01 * dw for w, dw in zip(params, grads)]
return params
使用 pjit() 進行平行化
接下來,我們可以使用 pjit() 來對模型進行平行化處理。pjit() 函式可以自動地將模型分割到多個裝置上,並進行平行計算。
# 定義 pjit() 函式
f_update = jax.pjit(update,
in_shardings=(None, jax.sharding.PartitionSpec('x'), jax.sharding.PartitionSpec('x')),
out_shardings=None)
# 定義批次精確度計算函式
def batch_accuracy(params, images, targets):
images = jnp.reshape(images, (len(images), NUM_PIXELS))
predicted_class = jnp.argmax(batched_predict(params, images), axis=1)
return jnp.mean(predicted_class == targets)
# 對批次精確度計算函式進行平行化
f_batch_accuracy = jax.pjit(batch_accuracy,
in_shardings=(None, jax.sharding.PartitionSpec('x'), jax.sharding.PartitionSpec('x')))
訓練迴圈
最後,我們可以組織訓練迴圈,使用 pjit() 函式來進行平行化計算。
# 定義裝置列表
devices = jax.devices()
# 訓練迴圈
for epoch in range(NUM_EPOCHS):
# 更新引數
params = f_update(params, x, y)
# 計算批次精確度
accuracy = f_batch_accuracy(params, x, y)
# 輸出精確度
print(f'Epoch {epoch+1}, Accuracy: {accuracy:.4f}')
深度學習模型的準確率計算
在深度學習中,準確率是評估模型效能的重要指標。以下是計算準確率的步驟和相關程式碼。
準備工作
首先,我們需要準備好模型的引數和資料。這包括定義模型的架構、載入訓練好的模型權重以及準備好要評估的資料集。
Shardings 和 Mesh
在分散式計算中,Shardings 和 Mesh 是兩個重要的概念。Shardings 指的是將資料或模型引數分割成多個部分,以便於平行計算。Mesh 則是指用於組織和管理這些分割部分的結構。在以下的程式碼中,我們將使用 pjit 函式來建立一個 Mesh,並將模型引數和資料分佈在這個 Mesh 上。
準確率計算函式
以下是計算準確率的函式:
def accuracy(params, data):
#...
# Calls pjit
#...
# Prepares device mesh
# Creates JIT-compiled sharded function for calculating accuracy on batch
accs = []
# Shards both x and y parameters across the "x" mesh axis (this will be the batch axis)
# Output is not sharded.
#...
return accs
在這個函式中,我們首先建立一個 Mesh,並將模型引數和資料分佈在這個 Mesh 上。然後,我們使用 pjit 函式來建立一個 JIT-compiled 的函式,這個函式可以在批次上計算準確率。最後,我們傳回計算出的準確率列表。
內容解密:
在上面的程式碼中,pjit 函式是用來建立一個 JIT-compiled 的函式,這個函式可以在批次上計算準確率。Shards both x and y parameters across the "x" mesh axis 的意思是將模型引數和資料分佈在 Mesh 的 “x” 軸上,這個軸對應於批次軸。Output is not sharded 的意思是輸出不進行分割。
圖表翻譯:
以下是上述程式碼的 Mermaid 圖表:
flowchart TD
A[準備工作] --> B[建立 Mesh]
B --> C[分佈模型引數和資料]
C --> D[計算準確率]
D --> E[傳回準確率列表]
這個圖表展示了計算準確率的步驟,從準備工作開始,到建立 Mesh、分佈模型引數和資料,然後計算準確率,最後傳回準確率列表。
平行化實驗
在深度學習中,平行化是一種重要的技術,用於加速模型的訓練過程。下面,我們將探討如何對影像和目標引數進行平行化處理。
影像和目標引數的平行化
我們可以將影像和目標引數分割成多個部分,以便在多個裝置上進行平行計算。這樣可以大大提高計算效率。
# 對影像和目標引數進行平行化處理
for images, targets in data:
# 對每個影像和目標進行計算
accs.append(f_batch_accuracy(params, images, targets))
return jnp.mean(jnp.array(accs))
時間測量和效能最佳化
在進行平行化實驗時,時間測量是一個重要的方面。下面,我們使用 time 模組來測量每個 epoch 的訓練時間。
import time
# 初始化引數
params = init_params
# 啟動 Mesh 並指定裝置
with Mesh(devices, ('x',)):
# 迭代多個 epoch
for epoch in range(NUM_EPOCHS):
# 記錄開始時間
start_time = time.time()
# 初始化損失列表
losses = []
# 迭代訓練資料
for x, y in train_data:
# 對影像進行重塑
x = jnp.reshape(x, (len(x), NUM_PIXELS))
# 對目標進行 one-hot 編碼
y = one_hot(y, NUM_LABELS)
# 更新引數和計算損失
params, loss_value = f_update(params, x, y, epoch)
# 追加損失值
losses.append(jnp.sum(loss_value))
# 計算 epoch 時間
epoch_time = time.time() - start_time
# 計算訓練準確率
train_acc = accuracy(params, train_data)
結果分析和最佳化
透過上述實驗,我們可以得到每個 epoch 的訓練時間和準確率。這些結果可以幫助我們最佳化模型的效能和平行化策略。
內容解密:
在上述程式碼中,我們使用 jnp.mean 函式來計算平均準確率,jnp.array 函式來建立陣列,jnp.reshape 函式來重塑影像,one_hot 函式來進行 one-hot 編碼。同時,我們使用 time.time 函式來測量時間,Mesh 類別來啟動平行化裝置。
圖表翻譯:
下面是 Mermaid 圖表,用於視覺化展示上述程式碼的執行流程:
flowchart TD
A[開始] --> B[初始化引數]
B --> C[啟動 Mesh]
C --> D[迭代 epoch]
D --> E[迭代訓練資料]
E --> F[更新引數和計算損失]
F --> G[計算 epoch 時間]
G --> H[計算訓練準確率]
H --> I[結束]
這個圖表展示了從初始化引數到計算訓練準確率的整個過程。
從效能最佳化視角來看,pjit() 為 JAX 帶來了顯著的效能提升,尤其在處理大規模張量平行計算時。透過本文的實驗和程式碼範例,我們深入分析了 pjit() 如何將資料和模型引數分片到不同的裝置上,實作真正的平行化計算。利用 Mesh 和 PartitionSpec,我們可以精細地控制資料和模型的分割方式,最大程度地利用硬體資源。值得注意的是,pjit() 並非沒有限制。它在處理非均勻資料分割和複雜的通訊模式時,仍存在挑戰。同時,程式碼的調整也需要開發者仔細考量資料分佈和計算流程。
與傳統的資料平行化方法相比,pjit() 的張量平行化策略更能有效地應對模型引數爆炸式增長的趨勢。它允許將巨大的模型分割到多個裝置上,突破單個裝置的記憶體限制,從而訓練更複雜、更強大的模型。此外,pjit() 的自動 collective 操作簡化了跨裝置通訊的複雜性,降低了開發者的負擔。然而,開發者仍需仔細規劃分片策略,避免通訊瓶頸成為效能的阻礙。
展望未來,隨著硬體技術的發展和 JAX 生態的持續完善,pjit() 有望在更大規模的深度學習任務中發揮更重要的作用。預計未來 pjit() 將支援更靈活的分片策略和更最佳化的通訊演算法,進一步提升其效能和易用性。對於追求極致效能的深度學習研究者和工程師而言,深入理解和掌握 pjit() 的使用技巧將至關重要。對於重視長期發展的團隊,建議投入資源深入研究 pjit() 及其相關技術,以便在未來的深度學習浪潮中保持競爭力。