PyTorch 面试题
12 道题- 分类
- AI 与大模型
- 子分类
- ml
- 题目数
- 12 道
1 PyTorch Tensor 的内存布局与 Stride 机制是什么?与 NumPy 数组有何本质差异?
答案:
PyTorch Tensor 是封装了存储(Storage)、形状(Shape)、**步长(Stride)和数据类型(dtype)**的多维数组视图,同一 Storage 可被多个 Tensor 共享(零拷贝视图)。
内存布局核心:
# Tensor 元数据
tensor = torch.tensor([[1, 2, 3],
[4, 5, 6]])
# .storage() → 底层一维连续内存 [1, 2, 3, 4, 5, 6]
# .shape → torch.Size([2, 3])
# .stride() → (3, 1) # 沿 dim0 步长 3,沿 dim1 步长 1
# .is_contiguous() → True
Stride 机制示意:
graph TB
subgraph Storage["Storage 一维内存 [1 2 3 4 5 6]"]
S0["0:1"] --- S1["1:2"] --- S2["2:3"] --- S3["3:4"] --- S4["4:5"] --- S5["5:6"]
end
subgraph View2x3["View shape=(2,3) stride=(3,1)"]
V0["[0,0]=1"] --- V1["[0,1]=2"] --- V2["[0,2]=3"]
V3["[1,0]=4"] --- V4["[1,1]=5"] --- V5["[1,2]=6"]
end
subgraph View3x2["View shape=(3,2) stride=(2,1)"]
W0["[0,0]=1"] --- W1["[0,1]=2"]
W2["[1,0]=3"] --- W3["[1,1]=4"]
W4["[2,0]=5"] --- W5["[2,1]=6"]
end
Storage --> View2x3
Storage --> View3x2
关键操作:
| 操作 | 内存变化 | Stride 变化 | 拷贝 |
|---|---|---|---|
t.view(3, 2) | 共享 | 重新计算 | 无 |
t.t() 转置 | 共享 | 反转 | 无 |
t.contiguous() | 新建 | 重置为紧凑 | 有 |
t[0, :] 切片 | 共享 | 调整 | 无 |
t.clone() | 新建 | 同原 stride | 有 |
与 NumPy 差异:
| 维度 | PyTorch Tensor | NumPy ndarray |
|---|---|---|
| 计算设备 | CPU / CUDA / XPU / MPS | 仅 CPU |
| 自动微分 | 内置 autograd | 无 |
| 内存共享 | Tensor/Storage 分离 | 视图即共享 |
| 多进程安全 | multiprocessing 需 share_memory_() | 默认 fork 后安全 |
| 互操作 | tensor.numpy() / from_numpy() 零拷贝 | 需 torch.from_numpy() |
内存格式(Memory Format):
# Channels Last (NHWC) — 适合 CNN + cuDNN 加速
x_nhwc = x.contiguous(memory_format=torch.channels_last)
# 等价于 stride = (C, 1, W, C) # NHWC 排列
适用场景:
- 频繁 reshape/slice 场景:优先用
view(仅当连续时)否则用reshape - 转置后接卷积:必须
contiguous()一次 - 大模型推理:使用
set_to_none=True的优化器清空梯度,节省存储
2 Autograd 的计算图机制是什么?动态图与静态图的区别是什么?
答案:
PyTorch Autograd 基于反向模式自动微分(Reverse-Mode AD),通过动态构建有向无环图(DAG)记录 Tensor 之间的运算关系,反向传播时沿图回溯计算梯度。
计算图节点类型:
graph LR
subgraph Forward["前向传播 — 构建计算图"]
X["x
Leaf Node
requires_grad=True"] -->|"*w1+b1"| L1["Linear1"]
L1 --> ReLU["ReLU"] --> L2["Linear2"] --> Y["y_pred"]
Y -->|"MSELoss"| Loss["loss
Scalar"]
end
subgraph Backward["反向传播 — 拓扑序回溯"]
Loss -->|"∂L/∂y"| Y -->|"∂y/∂L2"| L2 -->|"∂L2/∂ReLU"| ReLU -->|"∂ReLU/∂L1"| L1 -->|"∂L1/∂x"| X
end
核心概念:
| 概念 | 说明 |
|---|---|
| Leaf Tensor | 用户直接创建的 requires_grad=True Tensor,保留 .grad |
| Function 节点 | 每次运算生成 torch.autograd.Function 子类,持有 forward/backward |
| grad_fn | 每个非叶 Tensor 持有的反向函数引用 |
| retain_graph | 默认反向传播后图释放,多次 backward 需 retain_graph=True |
| create_graph | 高阶导数需开启,新图可对当前图求导 |
动态图 vs 静态图:
| 维度 | PyTorch 动态图(Define-by-Run) | TensorFlow 1.x 静态图(Define-and-Run) |
|---|---|---|
| 构建时机 | 每次 forward 重建 | 第一次 sess.run 前构建 |
| 调试性 | 直接 pdb/断点,逻辑即 Python | 需 tfdbg/TensorBoard |
| 控制流 | 原生 Python if/for | 限制于 tf.cond/tf.while_loop |
| 优化空间 | 即时执行,JIT 难优化 | 可做常量折叠、算子融合 |
| 性能 | 单步略慢,调试快 | 部署快,但调试痛苦 |
| 代表改进 | torch.compile(TorchDynamo+Inductor) | tf.function(AutoGraph) |
torch.no_grad() 与 detach() 对比:
# 场景 1:推理评估,节省显存
with torch.no_grad():
y = model(x)
# 场景 2:从图中分离某中间变量
z = some_tensor.detach() # 返回同数据的新 Tensor,requires_grad=False
# 场景 3:冻结参数
for p in model.parameters():
p.requires_grad = False # 参与前向但不参与梯度计算
适用场景:
- 训练循环:保持动态图原生用法
- 部署推理:
@torch.no_grad()+model.eval()双管齐下 - 多任务/多损失:保留中间变量时用
retain_graph=True或分步 backward
3 nn.Module 的内部结构与钩子(Hook)机制有哪些?参数初始化策略如何选择?
答案:
nn.Module 是 PyTorch 模型的基类,本质是有状态计算容器,通过 __setattr__ 自动注册 Parameter / Module / Buffer 三个特殊对象。
内部结构:
class MyModel(nn.Module):
def __init__(self):
super().__init__()
# 1. nn.Parameter — 参与梯度更新的可学习参数
self.weight = nn.Parameter(torch.randn(3, 3))
# 2. nn.Module — 子模块,自动注册到 _modules
self.linear = nn.Linear(3, 3)
# 3. register_buffer — 不参与梯度但需随模型移动(device/.to())
self.register_buffer('running_mean', torch.zeros(3))
# 4. 注册到 _parameters / _buffers / _modules 三个 OrderedDict
对象注册机制:
| 赋值方式 | 注册位置 | 行为 |
|---|---|---|
self.param = nn.Parameter(...) | _parameters | .parameters() 迭代,自动 .to(device) |
self.layer = nn.Linear(...) | _modules | 递归访问,参与 state_dict |
self.register_buffer(name, t) | _buffers | state_dict 包含,.to() 跟随 |
self.x = torch.tensor(...) | 普通属性 | 不被注册,.to() 不动 |
钩子(Hook)机制:
graph TB
subgraph ForwardHook["Forward Hook — 提取中间特征"]
A["Module.forward"] --> B["register_forward_hook(fn)"] --> A2["fn(module, input, output)"]
end
subgraph BackwardHook["Backward Hook — 梯度分析/修改"]
C["loss.backward()"] --> D["register_full_backward_hook(fn)"] --> D2["fn(module, grad_input, grad_output)"]
end
subgraph TensorHook["Tensor Hook — 拦截张量梯度"]
E["x.register_hook(grad_fn)"] --> E2["grad_fn(grad) → new_grad"]
end
钩子类型对比:
| 钩子 API | 作用 | 典型用途 |
|---|---|---|
register_forward_hook | 提取/修改 forward 输出 | 特征可视化、注意力热力图 |
register_forward_pre_hook | forward 前修改输入 | 输入归一化、提示注入 |
register_full_backward_hook | 反向时获取梯度 | 梯度异常检测、梯度裁剪定位 |
register_backward_hook | 旧版,已弃用 | - |
Tensor.register_hook | 单个 Tensor 梯度拦截 | 梯度惩罚、对抗训练 FGSM |
参数初始化策略:
| 层类型 | 推荐初始化 | 说明 |
|---|---|---|
nn.Linear | kaiming_uniform_(默认) / xavier_uniform_ | 适配 ReLU/Tanh |
nn.Conv2d | kaiming_normal_(fan_out) | 适配 ReLU |
nn.Embedding | normal_(0, 1) | 截断正态更稳 |
nn.LSTM / GRU | orthogonal_ 循环权重 | 避免梯度爆炸/消失 |
nn.BatchNorm | 常量 1, 0 | 已有合理默认 |
| Transformer | TruncatedNormal(0, 0.02) | 参考 GPT/BERT 论文 |
Xavier(glorot) | tanh/sigmoid | 保持输入/输出方差一致 |
自定义初始化:
def init_weights(module):
if isinstance(module, nn.Linear):
nn.init.kaiming_normal_(module.weight, nonlinearity='relu')
if module.bias is not None:
nn.init.zeros_(module.bias)
elif isinstance(module, nn.LayerNorm):
nn.init.ones_(module.weight)
nn.init.zeros_(module.bias)
model.apply(init_weights) # 递归应用到所有子模块
适用场景:
- 多 GPU 模型:理解
module.to(device)自动递归 - 迁移学习:冻结 backbone 时检查
requires_grad而非修改 optimizer - 调试梯度:优先用
register_full_backward_hook而非打印loss
4 DataLoader 与 Dataset 的工作机制是什么?如何优化数据加载瓶颈?
答案:
Dataset 定义数据从哪里来(样本索引与读取),DataLoader 定义数据怎么喂给模型(批处理、并行、乱序、预取)。
核心组件:
graph LR
DS["Dataset
__len__() / __getitem__(idx)"] --> SAM["Sampler
索引序列
(Random/Sequential/Distributed)"]
SAM --> BS["BatchSampler
批级索引"]
BS --> COL["collate_fn
样本 → Batch Tensor"]
COL --> DP["DataLoader Workers
num_workers 进程池"]
DP --> PIN["pin_memory
锁页内存 → 异步 H2D 拷贝"]
PIN --> GPU["GPU"]
关键参数:
| 参数 | 作用 | 推荐值 |
|---|---|---|
num_workers | 数据加载并行进程数 | 4-16,CPU 核数 50%-80% |
pin_memory | 使用锁页内存,加速 tensor.to(device, non_blocking=True) | 训练时 True |
persistent_workers | 跨 epoch 保持 worker 进程 | True 避免重复 fork |
prefetch_factor | 每个 worker 预取的 batch 数 | 默认 2,IO 重时调高 |
drop_last | 丢弃最后不完整 batch | DDP 训练时必须 True |
multiprocessing_context | 进程启动方式 | Linux 默认 fork,Win/Mac 用 spawn |
自定义 Dataset 与 collate_fn:
class TextDataset(Dataset):
def __init__(self, texts, labels, tokenizer):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
# 单样本 IO 与预处理
enc = self.tokenizer(self.texts[idx], truncation=True, max_length=512)
return {
'input_ids': torch.tensor(enc['input_ids']),
'label': torch.tensor(self.labels[idx], dtype=torch.long)
}
def collate_fn(batch):
# 变长序列 padding
input_ids = [item['input_ids'] for item in batch]
padded = nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=0)
labels = torch.stack([item['label'] for item in batch])
return {'input_ids': padded, 'labels': labels}
IterableDataset 与流式数据:
class StreamDataset(IterableDataset):
def __iter__(self):
# 适用:超大文件、数据库流、网络流、TFRecord 多 shard
worker_info = torch.utils.data.get_worker_info()
for shard in self.shards[worker_info.id::worker_info.num_workers]:
for sample in self._read_shard(shard):
yield sample
性能瓶颈诊断:
| 现象 | 根因 | 解决方案 |
|---|---|---|
| GPU 利用率波动大,IO 等待 | num_workers=0 | 增大 num_workers、pin_memory=True |
| 训练卡顿 1-2 秒周期性 | persistent_workers=False,每 epoch fork | 开启 persistent_workers=True |
| CPU 占用 100% 仍慢 | __getitem__ 中 IO 阻塞 | 数据预加载到内存 / LMDB / WebDataset |
| 显存增长 | collate 后未释放 | 在 collate_fn 内显式 del 单样本 |
| 多卡 DDP 重复数据 | sampler 未用 DistributedSampler | 切换 DistributedSampler(dataset, shuffle=True) |
适用场景:
- 图像分类:可使用
torchvision.datasets.ImageFolder+WeightedRandomSampler平衡类别 - NLP 大模型:自实现
collator,动态 padding 减少 padding token - 多模态大模型:使用
WebDatasettar 流式,避免本地落盘
5 PyTorch 分布式训练的方案对比:DDP / FSDP / DeepSpeed 的原理与适用场景是什么?
答案:
PyTorch 分布式训练核心是数据并行——多卡各持一份模型副本、划分数据、同步梯度。演进路线为 DDP(单进程多卡)→ FSDP(参数分片)→ DeepSpeed(ZeRO 全套 + 推理优化)。
数据并行演进:
graph TB
subgraph DDP["DDP — 同步数据并行"]
D1["GPU 0: Model 完整副本"] -->|"AllReduce 梯度"| D2["GPU 1: Model 完整副本"]
D2 --> D3["GPU 2: Model 完整副本"]
D3 --> D4["GPU 3: Model 完整副本"]
end
subgraph FSDP["FSDP — 参数分片数据并行"]
F1["GPU 0: Layer0+Layer3"] -->|"AllGather 临时参数"| F2["GPU 1: Layer1+Layer4"]
F2 --> F3["GPU 2: Layer2+Layer5"]
F3 --> F4["GPU 3: Layer0 副本"]
end
subgraph DS["DeepSpeed ZeRO-3"]
Z1["GPU 0: Optimizer+Grad+Param 全分片"] --> Z2["GPU 1: 互补分片"]
Z2 --> Z3["GPU 2: 互补分片"]
Z3 --> Z4["GPU 3: 互补分片"]
end
方案对比:
| 维度 | DDP | FSDP | DeepSpeed ZeRO-3 |
|---|---|---|---|
| 参数位置 | 每卡完整一份 | 切分到各卡 | 切分到各卡 |
| 梯度位置 | 每卡完整一份 | 切分到各卡 | 切分到各卡 |
| 优化器状态 | 每卡完整一份 | 每卡完整一份 | 切分到各卡 |
| 通信原语 | Ring-AllReduce (NCCL) | AllGather + ReduceScatter | AllGather + ReduceScatter |
| 显存节省 | 1× | ~N/参层数 | 最高 3×N 节省 |
| 通信量 | 梯度大小 | 参数量 + 梯度 | 参数量 + 梯度 |
| 框架 | 官方 torch.distributed | 官方 torch.distributed.fsdp | 微软独立框架 |
| 适合规模 | 单机多卡 / 数十亿参数 | 70B+ 参数 | 100B+ / 极致优化 |
| 易用性 | 最高 | 中等 | 中等(需 ds config) |
| 推理优化 | 无 | 无 | deepspeed-inference 内核融合 |
DDP 启动方式:
# torchrun --nproc_per_node=4 train.py
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
dist.init_process_group(backend='nccl')
local_rank = int(os.environ['LOCAL_RANK'])
torch.cuda.set_device(local_rank)
model = MyModel().cuda(local_rank)
model = DDP(model, device_ids=[local_rank], find_unused_parameters=False)
sampler = DistributedSampler(dataset, shuffle=True)
loader = DataLoader(dataset, batch_size=32, sampler=sampler, drop_last=True)
FSDP 配置示例:
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import MixedPrecision, BackwardPrefetch, ShardingStrategy
# auto_wrap_policy: 按层大小自动分片
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
wrap_policy = functools.partial(
transformer_auto_wrap_policy,
transformer_layer_cls={TransformerBlock} # 自定义 Block 类
)
model = FSDP(
model,
sharding_strategy=ShardingStrategy.FULL_SHARD, # ZeRO-3 等价
mixed_precision=MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.bfloat16,
buffer_dtype=torch.bfloat16,
),
auto_wrap_policy=wrap_policy,
backward_prefetch=BackwardPrefetch.BACKWARD_PRE,
device_id=local_rank,
)
DeepSpeed 启动:
# deepspeed --num_gpus=4 train.py
import deepspeed
model_engine, optimizer, _, scheduler = deepspeed.initialize(
model=model,
model_parameters=model.parameters(),
config='ds_config.json'
)
# ds_config.json
{
"train_batch_size": 32,
"fp16": {"enabled": true},
"zero_optimization": {
"stage": 3,
"offload_optimizer": {"device": "cpu"},
"offload_param": {"device": "cpu"}
},
"activation_checkpointing": {"partition_activations": true}
}
通信后端选择:
| 后端 | 适用 |
|---|---|
nccl | GPU ↔ GPU(首选,NCCL) |
gloo | CPU 通信,调试小规模 |
mpi | HPC 集群,含 InfiniBand 优化 |
适用场景:
- 单机 8 卡训练 BERT-base:优先 DDP
- 8 机 64 卡训练 LLaMA-70B:FSDP FULL_SHARD + BF16
- 100B+ 含 CPU Offload:DeepSpeed ZeRO-3 + Offload + Activation Checkpointing
- 集群含 InfiniBand:启用
NCCL_IB_HCA=mlx5环境变量
6 混合精度训练的 FP16 / BF16 / FP32 机制是什么?AMP 的实现原理是什么?
答案:
混合精度训练通过低精度(FP16/BF16)做前向/反向,高精度(FP32)保存主权重与优化器状态,在几乎不损失模型精度的前提下,将显存占用减半、计算吞吐提升 2-3 倍。
IEEE 754 浮点格式对比:
graph TB
subgraph FP32["FP32 — 32 bit"]
A1["Sign: 1 bit"] --- A2["Exponent: 8 bit
范围 ±3.4e38"] --- A3["Mantissa: 23 bit
精度 ~7 位十进制"]
end
subgraph FP16["FP16 — 16 bit"]
B1["Sign: 1 bit"] --- B2["Exponent: 5 bit
范围 ±65504"] --- B3["Mantissa: 10 bit
精度 ~3 位十进制"]
end
subgraph BF16["BF16 — 16 bit"]
C1["Sign: 1 bit"] --- C2["Exponent: 8 bit
范围与 FP32 相同"] --- C3["Mantissa: 7 bit
精度低于 FP16"]
end
精度特性对比:
| 维度 | FP32 | FP16 | BF16 | TF32(Tensor Core) |
|---|---|---|---|---|
| 总位数 | 32 | 16 | 16 | 19(实际) |
| 指数位 | 8 | 5 | 8 | 8 |
| 尾数位 | 23 | 10 | 7 | 10 |
| 表示范围 | ±3.4e38 | ±65504 | ±3.4e38 | ±3.4e38 |
| 数值精度 | 高 | 中 | 低 | 中 |
| 溢出风险 | 无 | 高 | 无 | 无 |
| 适用 | 训练主权重 | A100/V100 计算 | A100+/TPU/NPU | 矩阵乘 fallback |
Loss Scaling 解决 FP16 下溢:
graph LR
L["loss"] -->|"× scale (e.g. 65536)"| ScaledL["scaled_loss"]
ScaledL -->|"backward → FP16 梯度"| Grad["FP16 gradient
(无下溢)"]
Grad -->|"÷ scale"| RealGrad["FP16 真实梯度"]
RealGrad -->|"optimizer.step()"| W["FP32 master weight 更新"]
AMP(Automatic Mixed Precision)实现:
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler() # FP16 专用,BF16 无需 scaler
for x, y in loader:
optimizer.zero_grad(set_to_none=True)
with autocast(dtype=torch.float16): # 自动算子级别精度选择
y_pred = model(x)
loss = criterion(y_pred, y)
scaler.scale(loss).backward() # loss × scale
scaler.unscale_(optimizer) # 梯度 ÷ scale
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # 裁剪未缩放梯度
scaler.step(optimizer) # 检测 inf/nan
scaler.update() # 动态调整 scale
autocast 算子级精度白名单:
| 算子类型 | autocast 行为 |
|---|---|
nn.Linear、nn.Conv*、nn.GRU/LSTM | 自动 FP16 |
nn.BatchNorm、nn.LayerNorm | 保持 FP32(数值稳定) |
nn.Softmax、nn.CrossEntropyLoss | 保持 FP32(损失需精度) |
torch.matmul、torch.bmm | 自动 FP16 |
逐元素 add/mul/sqrt | 自动 FP16 |
BF16 训练(PyTorch 1.12+ 推荐):
# BF16 无需 Loss Scaling,指数范围与 FP32 相同
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
y_pred = model(x)
loss = criterion(y_pred, y)
loss.backward()
optimizer.step()
FP16 vs BF16 选型:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 训练 ResNet/BERT | BF16 | 无 Loss Scaling,简化代码 |
| 训练 GAN(判别器梯度大) | BF16 | 不易溢出 |
| 部署推理到 V100/T4 | FP16 | 这些卡 BF16 慢 |
| 部署到 A100/H100 | BF16 | 硬件原生支持 |
| 微调 LLM | BF16 | 主流标准 |
适用场景:
- 大模型训练:BF16 + FSDP/DeepSpeed
- 视觉模型:BF16 或 FP16 + GradScaler
- 分布式推理:BF16(无需 scaler,吞吐量更高)
7 模型保存与加载的 state_dict 机制是什么?断点续训如何正确实现?
答案:
PyTorch 推荐使用 state_dict(OrderedDict) 保存模型参数、缓冲区、优化器状态、调度器状态,而非直接 pickle 整个对象(兼容性差、安全风险)。
保存对象分类:
| 对象 | 内容 | 推荐保存方式 |
|---|---|---|
| 模型参数 | model.state_dict() | torch.save(model.state_dict(), 'model.pt') |
| 优化器状态 | optimizer.state_dict() | 包含动量、Adam 一阶/二阶矩、学习率等 |
| 学习率调度器 | scheduler.state_dict() | 保存当前 epoch 与 step |
| 训练元信息 | epoch、global_step、best_metric | 自定义 dict |
| 数据集/Tokenizer 路径 | 引用 | 配置文件中保存 |
保存与加载代码:
# === 保存 ===
checkpoint = {
'epoch': epoch,
'global_step': global_step,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'scheduler_state_dict': scheduler.state_dict(),
'scaler_state_dict': scaler.state_dict() if scaler else None,
'best_metric': best_metric,
'rng_state': torch.get_rng_state(), # CPU 随机状态
'cuda_rng_state': torch.cuda.get_rng_state_all(), # 所有 GPU RNG
}
torch.save(checkpoint, f'ckpt_epoch{epoch}.pt')
# === 加载(断点续训) ===
checkpoint = torch.load('ckpt_epoch10.pt', map_location='cpu')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
if scaler and checkpoint['scaler_state_dict']:
scaler.load_state_dict(checkpoint['scaler_state_dict'])
start_epoch = checkpoint['epoch'] + 1
global_step = checkpoint['global_step']
torch.set_rng_state(checkpoint['rng_state'])
torch.cuda.set_rng_state_all(checkpoint['cuda_rng_state'])
DDP 与 FSDP 加载兼容:
# DDP 模型的 state_dict 多 'module.' 前缀
# 方案 1:先剥前缀再 load
state_dict = {k.replace('module.', ''): v for k, v in ckpt.items()}
model.load_state_dict(state_dict)
# 方案 2:使用 model.module 访问
model.module.load_state_dict(ckpt)
# FSDP 加载(PyTorch 2.x 支持直接 load 完整状态)
from torch.distributed.fsdp import FullyShardedDataParallel, StateDictType
with FSDP.state_dict_type(model, StateDictType.FULL_STATE_DICT):
model.load_state_dict(full_state_dict) # rank 0 广播
模型版本与格式:
| 格式 | 内容 | 适用 |
|---|---|---|
.pt / .pth | state_dict 或 pickle | PyTorch 原生 |
.safetensors | 张量(无 pickle) | HuggingFace 通用,零攻击面 |
.onnx | 跨框架计算图 | 跨语言部署 |
.bin | HF Transformers 旧格式 | HF 生态 |
TorchScript(.pt) | Python 子集静态图 | C++ 部署 |
| ExportedProgram | torch.export 输出 | AOT 编译 |
断点续训最佳实践:
# 1. 仅在 rank 0 保存
if dist.get_rank() == 0:
torch.save(checkpoint, path)
# 2. 滚动保存最近 N 个
ckpts = sorted(glob('ckpt_*.pt'), key=os.path.getmtime)
for old in ckpts[:-3]:
os.remove(old)
# 3. 保存 EMA 模型(推理质量更稳)
ema_model = torch.optim.swa_utils.AveragedModel(model)
torch.save(ema_model.module.state_dict(), 'ema.pt')
# 4. 异步保存避免 IO 阻塞
import threading
def async_save(obj, path):
threading.Thread(target=torch.save, args=(obj, path), daemon=True).start()
safetensors 优势:
- 零拷贝内存映射,毫秒级加载
- 无 pickle 反序列化漏洞
- HuggingFace Hub 标准格式
- PyTorch 1.13+ 内置支持
适用场景:
- 训练恢复:完整保存 checkpoint + RNG state
- 推理部署:仅保存 state_dict 或导出为 ONNX/TorchScript
- 模型共享:上传到 HuggingFace Hub 使用 safetensors 格式
8 ONNX 导出的原理与陷阱是什么?动态 shape 与算子兼容性如何处理?
答案:
ONNX(Open Neural Network Exchange)是跨框架模型表示标准,将 PyTorch 动态图转换为静态计算图(protobuf 格式),通过 ONNX Runtime(ORT)在多语言、多硬件上高效推理。
导出流程:
graph LR
P["PyTorch 模型
(Python)"] -->|"torch.onnx.export()"| T["TorchScript
(traced graph)"]
T -->|"onnx.proto 序列化"| O["ONNX Model
(.onnx)"]
O -->|"onnxruntime / TensorRT / OpenVINO"| D["部署
(C++/Java/C#/Python)"]
torch.onnx.export 核心参数:
torch.onnx.export(
model, # nn.Module
(dummy_input,), # 示例输入(tuple)
"model.onnx",
input_names=['input_ids'], # ONNX 图输入名
output_names=['logits'],
dynamic_axes={ # 动态维度
'input_ids': {0: 'batch', 1: 'sequence'},
'logits': {0: 'batch', 1: 'sequence'},
},
opset_version=17, # ONNX 算子集版本
do_constant_folding=True, # 常量折叠优化
training=torch.onnx.TrainingMode.EVAL, # 推理模式
)
追踪(Trace)vs 脚本(Script):
| 维度 | torch.onnx.export 默认 Trace | torch.jit.script |
|---|---|---|
| 原理 | 运行一次 forward 记录算子 | 解析 Python AST 编译为 TorchScript |
| 适用 | 无控制流的模型 | 包含 if/for/数据依赖分支 |
| 限制 | 不支持动态控制流、张量形状依赖输入 | 限制 Python 子集 |
| 失败常见 | if x.shape[0] > 0: 会被烧死 | torch.jit.script(model) |
处理动态控制流:
# 方案 1:重写为控制流算子
# 改 if 为 torch.cond,for 为 loop.unroll
from torch.onnx import export
# 方案 2:先用 torch.jit.script 转 TorchScript 再 export
scripted = torch.jit.script(model)
torch.onnx.export(scripted, dummy_input, "model.onnx", ...)
# 方案 3:自定义 onnx-symbolic
class MyOp(torch.autograd.Function):
@staticmethod
def symbolic(g, x):
return g.op("custom::MyOp", x) # 映射到自定义 ONNX 算子
常见 ONNX 不兼容算子:
| PyTorch 算子 | ONNX 对应 | 解决方案 |
|---|---|---|
F.grid_sample | onnx::GridSample | 限制 mode='bilinear', padding_mode='zeros' |
F.pixel_shuffle | 无 | 使用 Reshape + Conv + Reshape 替代 |
nn.functional.scaled_dot_product_attention | onnx::SDPA(opset 14+) | 升级 opset,或 fallback 到手动实现 |
torch.unique、torch.scatter_add | 需 opset 16+ | 升级 opset_version |
| 自定义 Triton/CUDA kernel | 无 | 重写为 PyTorch 原生算子 |
torch.where + 复杂条件 | 需 Trace 等价分支 | 拆分模型分别导出 |
验证导出正确性:
import onnx
import onnxruntime as ort
import numpy as np
# 1. 检查模型结构
model = onnx.load("model.onnx")
onnx.checker.check_model(model) # 拓扑正确性
# 2. 数值一致性验证
ort_session = ort.InferenceSession("model.onnx", providers=['CUDAExecutionProvider'])
ort_inputs = {k: v.cpu().numpy() for k, v in inputs.items()}
ort_output = ort_session.run(None, ort_inputs)[0]
torch_output = model(**inputs).logits.cpu().numpy()
np.testing.assert_allclose(ort_output, torch_output, rtol=1e-3, atol=1e-4)
性能优化路径:
graph LR
A[".onnx 文件"] --> B["onnxruntime"]
A --> C["TensorRT"]
A --> D["OpenVINO"]
A --> E["ONNX Runtime + DirectML"]
B --> B1["CPU/GPU 通用"]
C --> C1["NVIDIA GPU 极致性能"]
D --> D1["Intel CPU/GPU"]
E --> E1["Windows / AMD GPU"]
适用场景:
- 跨语言部署:C++/Java 后端服务选 ONNX
- 边缘设备:ONNX Runtime Mobile(iOS/Android)
- 极致性能:NVIDIA 部署转 TensorRT(需先 ONNX 中转)
- 浏览器推理:ONNX Runtime Web + WASM/WebGL
9 PyTorch 与 TensorFlow 的核心架构差异是什么?2024-2026 年的生态格局如何?
答案:
PyTorch 与 TensorFlow 是当前最主流的两个深度学习框架,设计哲学、API 风格、生产部署路径均有显著差异。PyTorch 在研究界占主导,TensorFlow 在工业部署仍有优势,但 TorchServe/Triton 缩小了差距。
核心架构对比:
| 维度 | PyTorch 2.x | TensorFlow 2.x |
|---|---|---|
| 计算图 | 动态图为主(eager),可选 torch.compile 转静态 | 默认 eager,tf.function 转静态图 |
| Python 集成 | 原生 Python 体验,调试直接 | tf.function 内部难调试 |
| 分布式 | 官方 DDP/FSDP/TensorParallel | tf.distribute.Strategy |
| 移动端 | PyTorch Mobile | TensorFlow Lite |
| 工业部署 | TorchServe / Triton / ONNX | TF Serving / TFLite / TFX |
| 编译优化 | TorchDynamo + TorchInductor | XLA / MLIR |
| 主导场景 | 研究、LLM 训练 | 工业生产、Android 端 |
生态格局(2024-2026 年):
| 生态组件 | PyTorch 主导 | TensorFlow 主导 |
|---|---|---|
| 顶会论文(NeurIPS/ICML) | 85%+ | <10% |
| HuggingFace 模型 | 99% PyTorch | 极少 |
| LLM 训练框架 | Megatron-LM、DeepSpeed、FSDP | 边缘 |
| 工业 CV 部署 | YOLOv8/v9(PyTorch) | TFLite(Android) |
| 学术研究 | 绝对主流 | 持续萎缩 |
| 移动端推理 | PyTorch Mobile(追赶中) | TFLite(成熟) |
| 浏览器推理 | ONNX Runtime Web | TensorFlow.js |
| JAX 份额 | 上升中(Google 内部) | 一定份额 |
关键 API 习惯差异:
# === 模型定义 ===
# PyTorch
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Linear(784, 10)
def forward(self, x):
return self.fc(x)
# TensorFlow
class Net(tf.keras.Model):
def __init__(self):
super().__init__()
self.fc = layers.Dense(10)
def call(self, x):
return self.fc(x)
# === 训练循环 ===
# PyTorch(显式)
optimizer.zero_grad()
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
# TF(Keras fit 或 GradientTape)
@tf.function
def train_step(x, y):
with tf.GradientTape() as tape:
loss = criterion(model(x), y)
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
PyTorch 2.x 重大改进:
# 1. torch.compile — 一行代码加速 1.5-3x
model = torch.compile(model, mode='reduce-overhead', fullgraph=True)
# 2. torch.export — AOT 编译,替代 torch.jit.trace
exported = torch.export.export(model, (dummy_input,))
# 3. FSDP v2 — 改进通信与显存
# 4. FlexAttention — 自定义注意力无 Triton 经验
# 5. torchao — 原生量化与稀疏化
TensorFlow 仍具优势的场景:
- TPU 训练(TPU 官方支持,PyTorch 需 XLA 桥接)
- 移动端 Android 端 TFLite 部署
- 与 Google Cloud 生态(TFX、Vertex AI)深度集成
- 数字信号处理与推荐系统 TFRS
选型决策:
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| LLM 训练/微调 | PyTorch | FSDP/DeepSpeed/Megatron 全套 |
| CV 研究实验 | PyTorch | 学术主流 |
| Android 端推理 | TensorFlow Lite | TFLite 优化成熟 |
| TPU 训练 | TF / JAX | 硬件原生 |
| 多语言后端部署 | PyTorch + ONNX | 跨语言标准 |
| 已有 TF 历史系统 | 维持 TF | 迁移成本高 |
适用场景:
- 新项目 AI/LLM:PyTorch 2.x + HuggingFace
- 工业视觉 Android 部署:TFLite
- 研究阶段:PyTorch 优先
10 torch.compile 的工作原理是什么?TorchDynamo + TorchInductor 链路如何工作?
答案:
torch.compile(PyTorch 2.0 推出)是基于 TorchDynamo(前端图捕获)+ TorchInductor(后端代码生成)+ AOTAutograd 的全栈 JIT 编译方案,能在不改模型代码的前提下获得 1.5-3× 训练加速。
编译流水线:
graph TB
P["Python 模型代码"] -->|"torch.compile(model)"| D["TorchDynamo
bytecode 注入
跟踪 Python 栈"]
D -->|"FX Graph"| A["AOTAutograd
反向图生成"]
A -->|"joint graph"| I["TorchInductor
算子融合 + 代码生成"]
I -->|"Triton / C++ Kernel"| K["优化 Kernel"]
K -->|"cuBLAS / cuDNN / Triton"| GPU["GPU 执行"]
D -.->|"Graph Break
遇到不支持的操作"| B["Fallback
单步 eager 执行"]
B -.-> P
TorchDynamo(前端):
- bytecode 钩子:
sys.settrace拦截 Python 字节码 - Graph 构造:将 Python 操作转为 FX Graph(PyTorch 中间表示)
- Graph Break:遇到无法追踪的操作(数据依赖
if、第三方 C 代码)时切分子图,剩余部分继续 eager - Guard:记录输入 shape/dtype/device,运行时验证,不匹配则重新编译
AOTAutograd(自动微分):
- 在前向图上预生成反向图(joint graph)
- 减少 Python 解释开销
- 支持更多 fused backward 操作
TorchInductor(后端):
- 算子融合(
matmul + add + relu→ 单个 Triton kernel) - 内存规划(避免中间张量分配)
- 自动选择最优 tile 大小
- 生成 Triton(GPU)或 C++/OpenMP(CPU)代码
使用方式与编译模式:
# 基础用法
model = torch.compile(model)
# 指定模式
model = torch.compile(model, mode='default') # 平衡编译时间和运行效率
model = torch.compile(model, mode='reduce-overhead') # 减少 Python 开销,CUDA Graph
model = torch.compile(model, mode='max-autotune') # 最大化性能,时间最长
model = torch.compile(model, mode='max-autotune-no-cudagraphs') # 不使用 CUDA Graph
# fullgraph=True:禁止 Graph Break
model = torch.compile(model, fullgraph=True)
# 单独编译某个函数
@torch.compile
def my_function(x, y):
return torch.relu(x @ y + 1)
编译模式对比:
| 模式 | 编译时间 | 加速比 | 适用 |
|---|---|---|---|
default | 中 | 1.3-2× | 通用 |
reduce-overhead | 中 | 1.5-2.5× | 小 batch + 频繁 Python 调用 |
max-autotune | 长(数分钟) | 2-3× | 生产部署、训练 LLM |
max-autotune-no-cudagraphs | 中-长 | 1.5-2.5× | 不支持 CUDA Graph 的场景 |
常见 Graph Break 原因与解决:
| 原因 | 解决 |
|---|---|
if tensor.shape[0] > 0: | 用 torch.cond 改写 |
print(tensor) | 训练中避免 print |
.item()、.tolist() | 避免在 hot path 调用 |
torch.cuda.synchronize() | 移除 |
自定义 Python __call__ | 重写为 forward |
| 第三方库 | 用 torch._dynamo.allow_in_graph() 标记 |
调试与回退:
# 1. 查看 Graph Break 位置
import torch._dynamo
torch._dynamo.config.verbose = True
torch._dynamo.config.suppress_errors = False # 失败时抛错而非回退
# 2. 单独回退某个函数
@torch._dynamo.disable
def untraceable_function(x):
...
# 3. 查看编译后的 FX Graph
from torch._dynamo.utils import counters
print(counters) # 统计 graph break 次数
# 4. 导出 FX Graph 调试
fx_graph = torch._dynamo.export(model)(dummy_input)
fx_graph.print_readable()
性能案例:
# 简单 MLP 训练
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(1024, 1024), nn.ReLU(),
nn.Linear(1024, 1024), nn.ReLU(),
nn.Linear(1024, 10)
)
def forward(self, x):
return self.layers(x)
# RTX 4090, batch=4096, FP16
# 训练吞吐量
# Eager: ~180k samples/s
# torch.compile: ~340k samples/s # 1.9x
# compile + AMP: ~420k samples/s # 2.3x
适用场景:
- 大模型训练:
torch.compile(model, mode='reduce-overhead')+ DDP - 推理服务:
mode='max-autotune'配合torch.export()导出 - 调试模型:保持 eager 模式,使用
torch.compile(backend='eager')验证兼容性
11 CUDA Stream 的并发机制是什么?PyTorch 多流训练的实现方式有哪些?
答案:
CUDA Stream 是 GPU 上的命令队列,同一 Stream 内操作严格顺序执行,不同 Stream 间可并发执行。合理利用多流能隐藏数据传输(H2D/D2H)与 kernel 调度开销,提升 GPU 利用率。
Stream 与并发模型:
graph TB
subgraph Default["Default Stream — 串行执行"]
D1["kernel A"] -->|"同步"| D2["kernel B"] -->|"同步"| D3["kernel C"]
end
subgraph MultiStream["多 Stream — 并发执行"]
S1["Stream 1: kernel A"] -.-> S2["Stream 1: kernel B"]
T1["Stream 2: kernel C"] -.-> T2["Stream 2: kernel D"]
S1 -.并发.-> T1
end
CPU["CPU 异步发射"] -->|"Stream 1"| S1
CPU -->|"Stream 2"| T1
核心 API:
# 创建 Stream
stream1 = torch.cuda.Stream()
stream2 = torch.cuda.Stream()
# Stream 间同步
event = torch.cuda.Event()
with torch.cuda.stream(stream1):
tensor1 = compute_a() # 在 stream1 上
event.record(stream1) # 记录事件
stream2.wait_event(event) # stream2 等待
with torch.cuda.stream(stream2):
tensor2 = compute_b(tensor1) # 自动同步
# 设备级同步(阻塞当前设备)
torch.cuda.synchronize()
PyTorch 多流训练模式:
# 模式 1:数据预取与计算重叠
class PrefetchLoader:
def __init__(self, loader, device):
self.loader = loader
self.device = device
self.stream = torch.cuda.Stream()
self.next_batch = None
def __iter__(self):
loader_iter = iter(self.loader)
self._preload(loader_iter)
return self
def _preload(self, loader_iter):
try:
self.next_batch = next(loader_iter)
except StopIteration:
self.next_batch = None
return
with torch.cuda.stream(self.stream):
self.next_batch = {
k: v.to(self.device, non_blocking=True)
for k, v in self.next_batch.items()
}
self.stream.synchronize() # 必须 sync,否则竞争
def __next__(self):
batch = self.next_batch
if batch is None:
raise StopIteration
self._preload(loader_iter) # 后台预取下一个 batch
return batch
Stream 限制:
- 同一 Stream 内:操作严格顺序
- 跨 Stream:默认独立,资源争抢时仍需
synchronize或wait_event cudaMemcpyAsync跨 stream 时需要 pinned memorycudaStreamLegacy(default stream)与其他 stream 同步语义特殊
实际性能影响:
| 场景 | 单 Stream | 多 Stream | 收益 |
|---|---|---|---|
| 纯计算瓶颈 | 100% 利用率 | 提升有限 | <5% |
| 频繁 H2D + 计算 | 串行 | 重叠 | 20-50% |
| 推理多 batch | 队列等待 | 并行 | 30-100% |
| 大模型 layer 间 | 顺序 | 部分重叠 | 5-15% |
通信与计算重叠:
# DDP 中梯度 bucket 内 AllReduce 与反向计算重叠
# 由 DDP 自动管理,无需手动
# FSDP 中 AllGather 与前向/反向计算重叠
# 由 FSDP 调度,无需手动
CUDA Graph 进一步降低发射开销:
# 适合小模型、固定 shape、高频调用
g = torch.cuda.CUDAGraph()
# 预热
for _ in range(3):
static_output = model(static_input)
# 捕获
with torch.cuda.graph(g):
static_output = model(static_input)
# 复用
for input_batch in loader:
static_input.copy_(input_batch)
g.replay()
# 复用 static_output
non_blocking=True 的作用:
- H2D 拷贝异步执行,前提是
pin_memory=True - 计算可与拷贝重叠
- 错误用法:
tensor.to('cuda', non_blocking=True)后立即tensor.sum(),仍会同步等待
适用场景:
- 数据加载瓶颈:预取 + 多 stream 重叠
- 推理服务多请求:每个请求独立 stream
- 通信与计算重叠:DDP/FSDP 已自动实现
12 PyTorch 性能调优的完整方法论是什么?profiler 与生产部署(Triton/TorchServe)方案是什么?
答案:
PyTorch 性能调优是一个测量→定位→优化→验证的迭代过程,需结合 torch.profiler、NCCL 通信分析、CUDA kernel 分析等多维度工具。
性能调优方法论:
graph TB
Start["训练/推理性能不足"] --> Baseline["建立基线
吞吐、延迟、显存"]
Baseline --> Profile["torch.profiler 分析
定位瓶颈类型"]
Profile --> Identify{"瓶颈在哪?"}
Identify -->|"DataLoader 等待"| D1["增大 num_workers
启用 pin_memory
预取到 GPU"]
Identify -->|"GPU 计算"| D2["torch.compile
AMP / BF16
算子替换 / 融合"]
Identify -->|"H2D 传输"| D3["pin_memory + non_blocking
多 Stream 异步"]
Identify -->|"通信(NCCL)"| D4["bucket 调优
overlap comm-compute
检查 IB/RoCE"]
Identify -->|"CPU 发射"| D5["CUDA Graph 捕获
reduce-overhead 模式"]
Identify -->|"显存"| D6["Gradient Checkpointing
FSDP / ZeRO
Activation Offload"]
D1 --> Verify["重新 profile 验证"]
D2 --> Verify
D3 --> Verify
D4 --> Verify
D5 --> Verify
D6 --> Verify
Verify -->|未达标| Profile
Verify -->|达标| Done["完成"]
torch.profiler 完整使用:
from torch.profiler import profile, ProfilerActivity, record_function, schedule
# 1. schedule:控制 profiler 范围
sched = schedule(wait=2, warmup=3, active=5, repeat=1)
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
schedule=sched,
on_trace_ready=torch.profiler.tensorboard_trace_handler('./log/prof'),
record_shapes=True,
profile_memory=True,
with_stack=True,
) as prof:
for step, (x, y) in enumerate(loader):
with record_function("forward"):
y_pred = model(x)
with record_function("loss_backward"):
loss = criterion(y_pred, y)
loss.backward()
optimizer.step()
prof.step()
# 2. 打印关键统计
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=15))
# 3. TensorBoard 可视化
# tensorboard --logdir=./log/prof
Profiler 关键指标:
| 指标 | 含义 | 异常 |
|---|---|---|
cuda_time_total | GPU 端总耗时 | 单算子占比过高 |
self_cuda_time_total | 不含子调用的耗时 | 区分 launch 与 compute |
cpu_time_total | CPU 发射开销 | 大量时间在 CPU 而非 GPU |
Self CUDA Memcpy | H2D/D2H 时间 | 数据传输瓶颈 |
NCCCL AllReduce | 通信耗时 | 通信占比 >30% 需优化 |
Memory Usage | 显存峰值 | OOM 前需要优化 |
常见性能瓶颈与对策:
| 瓶颈 | 诊断 | 解决 |
|---|---|---|
| DataLoader 慢 | profiler 看到 DataLoader 长 gap | num_workers=8、pin_memory=True、persistent_workers=True |
| CPU 发射慢 | cuda_time << cpu_time | torch.compile(mode='reduce-overhead')、CUDA Graph |
| GPU 利用率低 | nvidia-smi 显示 GPU 间歇 0% | 多流异步、消除 Python overhead |
| 通信瓶颈 | NCCl AllReduce 占比高 | DDP bucket_size 调大、NCCL_BUFFSIZE 调大 |
| 显存不足 | OOM 或频繁换页 | gradient_checkpointing()、FSDP ZeRO-2/3、混合精度 |
| Kernel 效率低 | cuDNN 占比高 | 调优 batch size、channels_last、TF32 |
生产部署方案对比:
| 方案 | 适用 | 优势 | 局限 |
|---|---|---|---|
| TorchServe | PyTorch 原生服务化 | 官方支持、模型归档、Metrics、Batch | 性能一般,扩展性有限 |
| Triton Inference Server | 高性能多框架 | Dynamic Batching、并发模型、TensorRT 后端 | 配置复杂,资源占用大 |
| ONNX Runtime | 跨语言跨平台 | 多语言 SDK、CPU/GPU 优化 | 部分自定义算子不兼容 |
| TensorRT | NVIDIA GPU 极致性能 | Kernel 融合、INT8/QAT、序列化引擎 | NVIDIA 锁定 |
| vLLM | LLM 推理 | PagedAttention、Continuous Batching | 专为 LLM 设计 |
| OpenVINO | Intel CPU/GPU/VPU | Intel 硬件加速 | 非 Intel 平台无效 |
| TGI | HuggingFace LLM 服务化 | Rust 实现、开箱即用 | 模型格式受限 |
TorchServe 示例:
# 安装
pip install torchserve torch-model-archiver
# 打包模型
torch-model-archiver \
--model-name resnet50 \
--version 1.0 \
--model-file model.py \
--serialized-file resnet50.pt \
--handler image_classifier
mkdir model_store && mv resnet50.mar model_store/
# 启动服务
torchserve --start \
--model-store model_store \
--models resnet50=resnet50.mar \
--ncs # 启用快照集成
Triton Inference Server 部署:
# 1. 导出 ONNX / TorchScript / TF SavedModel
torch.onnx.export(model, dummy, "model.onnx",
dynamic_axes={'input': {0: 'batch'}})
# 2. 配置 model_repository/
# model_repository/my_model/config.pbtxt
"""
name: "my_model"
platform: "onnxruntime_onnx"
max_batch_size: 64
input [
{
name: "input"
data_type: TYPE_FP32
dims: [3, 224, 224]
}
]
output [
{
name: "output"
data_type: TYPE_FP32
dims: [1000]
}
]
instance_group [{ kind: KIND_GPU, count: 2 }]
dynamic_batching { preferred_batch_size: [16, 32] }
"""
# 3. 启动
docker run --gpus all -p 8000:8000 -p 8001:8001 -p 8002:8002 \
-v $PWD/model_repository:/models nvcr.io/nvidia/tritonserver:24.08-py3 \
tritonserver --model-repository=/models
Triton 客户端调用:
import tritonclient.http as httpclient
triton_client = httpclient.InferenceServerClient(url="localhost:8000")
inputs = [httpclient.InferInput("input", [1, 3, 224, 224], "FP32")]
inputs[0].set_data_from_numpy(input_array)
outputs = [httpclient.InferRequestedOutput("output")]
response = triton_client.infer("my_model", inputs, outputs=outputs)
result = response.as_numpy("output")
生产部署决策树:
graph TB
Q[生产部署需求] --> Q1{目标硬件}
Q1 -->|NVIDIA GPU| Q2{需要极致性能?}
Q1 -->|Intel CPU| Q3[OpenVINO]
Q1 -->|多语言后端| Q4[ONNX Runtime]
Q1 -->|LLM 高吞吐| Q5[vLLM / TGI]
Q2 -->|是| Q6[TensorRT]
Q2 -->|否| Q7[Triton + ONNX]
Q2 -->|纯 PyTorch 生态| Q8[TorchServe]
适用场景:
- LLM 微调后服务化:Triton + vLLM
- CV 模型多租户:TorchServe 简单易用
- 跨语言(Java/Go)后端:ONNX Runtime
- NVIDIA 部署 + 极致延迟:TensorRT
- 端到端监控:Triton + Prometheus + Grafana