news 2026/9/28 21:21:26

昇腾NPU变长序列训练实战:从dynamic shape到variable_seq_lengths

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
昇腾NPU变长序列训练实战:从dynamic shape到variable_seq_lengths

1. 为什么“变长序列训练”在昇腾上不是加个 flag 就能跑通的事

你是不是也遇到过这样的场景:模型结构写好了,数据 pipeline 也按 PyTorch 惯例做了 padding + mask 处理,本地 CPU/GPU 上训得稳稳当当;一迁到昇腾 NPU 上,torch.compile()报错、aclrtCreateStream卡死、variable_seq_lengths参数传进去直接触发ACL_ERROR_INVALID_PARAM——连第一个 step 都没迈出去。这不是你代码写得不对,而是你误把昇腾当成了“另一个 CUDA”。

昇腾的变长序列训练,本质不是“支持动态 shape”,而是重构了整个执行流调度逻辑。CUDA 的 dynamic shape 依赖 runtime 层的 kernel dispatch 和 memory pool 自适应管理,而昇腾(尤其是昇腾310P3这类主流推理/训练一体卡)的底层 ACL(Ascend Computing Language)运行时,对 shape 变化极其敏感:它默认假设 tensor shape 在 graph compile 阶段就已固化,所有内存分配、算子融合、流水线调度都基于此前提。一旦你在 forward 过程中传入不同 batch 内各 sequence 长度差异超过阈值的 input_ids(比如 [128, 256, 512, 1024] 混合),ACL runtime 会立刻拒绝执行——因为它无法复用预编译的 kernel,也无法安全重用前序 batch 分配的 device memory。

我去年在某大厂做 LLaMA-3 7B 的昇腾适配时踩过最深的坑,就是以为torch.nn.utils.rnn.pad_sequence+attention_mask就够了。结果实测发现:哪怕只让 batch 内最大长度浮动 ±8 token,训练 loss 就开始剧烈震荡,GPU 上 0.002 的 loss,在昇腾上跳到 1.8,且梯度 norm 突然爆炸。后来抓取 ACL 日志才发现,根本不是模型出问题,而是AscendGraphExecutor在每次 shape 变更时强制触发 graph recompile,而 recompile 过程中旧 stream 未 clean,新 stream 未 sync,导致梯度计算和参数更新完全错位。

所以,“动态 shape”在昇腾语境下,从来不是一个配置开关,而是一整套从数据加载、图构建、内存管理到算子选择的协同设计。variable_seq_lengths不是让你传个 list[int] 就完事的 API,它是告诉 ACL:“接下来我要用一套全新的内存布局策略,请提前预留可变 size 的 buffer,并启用 subgraph partitioning”。这背后牵扯到昇腾 CANN(Compute Architecture for Neural Networks)工具链里三个关键模块的联动:ge(graph engine)、hccl(high-performance communication library)和acl(runtime)。漏掉其中任何一个环节,都会导致“配置写了,但没生效”——表面看是variable_seq_lengths=True,实际 runtime 还在用 static shape 模式跑。

这也是为什么热搜词里“昇腾310p3使用什么精度”“昇腾npu swift+megatron实战”会高频出现:大家真正卡住的,不是“能不能做”,而是“怎么做才不崩”。接下来我会一层层拆开这个黑盒,告诉你每个配置项背后的硬件约束、软件妥协和实操红线。

2. variable_seq_lengths 的真实作用域:它管不了 data loader,但管死了 graph 编译器

很多人把variable_seq_lengths当成一个全局开关,以为设成True后,整个训练流程就自动支持变长了。这是致命误解。variable_seq_lengths实际作用域非常窄,它只影响CANN Graph Engine(GE)在构建 Ascend Graph 时的 subgraph 划分策略和 memory planning 方式,与 PyTorch DataLoader、Collator、甚至模型 forward 中的 if-else 分支完全无关。

我们来看一个典型错误配置:

# ❌ 错误示范:以为设置这个就万事大吉 model = AutoModelForCausalLM.from_pretrained("llama-3-7b") model = torch.compile(model, backend="ascend", options={ "variable_seq_lengths": True, "dynamic_batch_size": True # 这个参数根本不存在!昇腾不支持动态 batch size })

这段代码的问题在于:torch.compile(..., backend="ascend")调用的是 CANN 提供的torch_npu插件,其options字典最终会被转换为 GE 的ge::GraphConfig。而variable_seq_lengths对应的是ge::GraphConfig::SetVariableSeqLengths(true),但它仅在 graph build 阶段生效,且必须满足两个前置条件:

  1. 输入 tensor 的 shape 必须声明为 dynamic:不能是torch.Size([bs, seq_len])这样的 concrete shape,而必须是torch.Size([bs, -1])或torch.Size([bs, None])(昇腾要求用-1表示 dynamic dim);
  2. 所有涉及 sequence length 的算子必须显式标注 dynamic shape 支持:比如torch.nn.functional.scaled_dot_product_attention在昇腾上需要调用npu_scaled_dot_product_attention,而后者内部会检查 input shape 是否含-1,再决定是否启用 variable-length kernel。

提示:昇腾官方文档里写的variable_seq_lengths=True是简化说法,实际底层要求是input_shape[1] == -1 && ge::GraphConfig::variable_seq_lengths_ == true。两者缺一不可,否则 GE 会静默 fallback 到 static mode,你根本收不到报错,但性能和正确性全无保障。

我实测过,如果只设variable_seq_lengths=True但 input shape 是 concrete(比如[4, 512]),GE 会照常 build graph,但所有 attention、layernorm、linear 算子都走 static kernel path,此时variable_seq_lengths形同虚设。只有当你把 dataloader 输出的 tensor shape 显式设为[-1, -1](batch dim 和 seq dim 都 dynamic),且在 model forward 中确保所有中间 tensor 的 shape 维持-1(比如x = x[:, :seq_len]会破坏 dynamic shape,必须用x = torch.npu.npu_slice(x, [0, 0], [-1, seq_len])),variable_seq_lengths才真正起效。

这里有个关键细节:昇腾的 dynamic shape 不是“运行时推导”,而是“编译时预留”。GE 在 build graph 时,会为每个-1维度预分配一个最大可能的 buffer(由max_seq_length参数控制),然后在 runtime 根据实际输入长度做 slice。所以max_seq_length不是性能参数,而是内存安全参数——设小了 runtime OOM,设大了显存浪费严重。我们团队在 310P3(32GB 显存)上跑 LLaMA-3 7B,max_seq_length=2048时显存占用 28.3GB,max_seq_length=4096直接 OOM,不是因为模型大,而是 GE 预分配的 buffer 超限。

3. 动态 shape 的三重校验:从数据加载到梯度回传的完整链路

昇腾上的变长序列训练,不是单点配置,而是一条贯穿数据、模型、runtime 的校验链。任何一环断裂,都会导致 silent failure(静默失败)——loss 不降、grad nan、甚至 loss 看似正常但 eval 结果全错。我把这条链拆成三个硬性关卡,每个关卡都有对应日志和验证方法。

3.1 数据关:Dataloader 输出必须带 dynamic shape annotation

PyTorch DataLoader 默认输出 concrete shape tensor。昇腾要求你主动注入 dynamic shape hint。有两种方式:

方式一:用torch.npu.dynamo的mark_dynamic(推荐)

from torch.npu.dynamo import mark_dynamic def collate_fn(batch): input_ids = pad_sequence([item["input_ids"] for item in batch], batch_first=True, padding_value=0) # 关键:标记 seq_len 维度为 dynamic mark_dynamic(input_ids, 1, -1) # dim=1 (seq_len), value=-1 return {"input_ids": input_ids} train_dataloader = DataLoader(dataset, batch_size=4, collate_fn=collate_fn)

mark_dynamic会在 tensor metadata 中写入dynamic_shape_info,GE build graph 时会读取该信息。注意:mark_dynamic必须在 tensor 创建后、进入 model 前调用,且只能调用一次。我试过在 collate_fn 里调用,但在 model forward 里又调用一次,结果 GE 直接 crash。

方式二:用torch._dynamo.config强制开启 dynamic shape inference(不推荐)

import torch._dynamo.config torch._dynamo.config.dynamic_shapes = True torch._dynamo.config.automatic_dynamic_shapes = True

这个配置会让 TorchDynamo 在 trace 时自动 infer dynamic dim,但昇腾兼容性极差——它会把input_ids.shape[1]推断为SymInt,而昇腾 GE 不认识 SymInt,最终 fallback 到 static mode。我们实测 100% 失败,日志里全是SymInt not supported in AscendGraph。

注意:mark_dynamic的 dim index 必须准确。LLaMA 类模型 input_ids shape 是[bs, seq_len],所以 mark dim=1;如果是[seq_len, bs](如某些 RNN 模型),则要 mark dim=0。标错维度,GE 会忽略该标记。

验证方法:在 dataloader 输出后加一行 debug:

batch = next(iter(train_dataloader)) print("Input shape:", batch["input_ids"].shape) # 应该是 torch.Size([4, -1]) print("Dynamic info:", batch["input_ids"].is_dynamic()) # 应该返回 True

如果is_dynamic()返回 False,说明mark_dynamic没生效,检查是否在 tensor detach 后调用(detach 会丢掉 dynamic info)。

3.2 模型关:forward 中所有 shape 变更操作必须用 NPU 原生 API

PyTorch 原生操作如x[:, :seq_len]、x.view(bs, -1, hidden)在昇腾上会破坏 dynamic shape。因为这些操作生成的新 tensor 会丢失 parent 的 dynamic annotation,且 shape 计算在 host 端完成,GE 无法感知。

必须全部替换为 NPU 原生 API:

PyTorch 原生操作昇腾 NPU 替代方案说明
x[:, :seq_len]torch.npu.npu_slice(x, [0, 0], [-1, seq_len])第二个参数是 start offset,第三个是 size;-1表示该 dim 全取
x.view(bs, -1, hidden)torch.npu.npu_reshape(x, [bs, -1, hidden])npu_reshape保留 dynamic dim,view不保留
x.transpose(0, 1)torch.npu.npu_transpose(x, [1, 0])transpose会丢失 dynamic info,npu_transpose保留

我曾经把x = x[:, :self.max_seq_len]漏掉改,结果训练 loss 看似收敛,但生成文本全是乱码。抓取 NPU profiling 发现:npu_slicekernel 耗时 0.3ms,而原生 slice 耗时 12ms 且触发 host-device sync,导致后续 attention kernel 输入 shape 错误。

3.3 Runtime 关:ACL stream sync 与 gradient accumulation 的耦合陷阱

昇腾的梯度累积(gradient accumulation)和 variable_seq_lengths 存在隐式耦合。当你启用variable_seq_lengths=True,ACL runtime 会为每个 batch 分配独立的 memory pool,且 stream sync 策略变为 per-batch granularity。如果你用 PyTorch 原生的optimizer.step()+optimizer.zero_grad(),在 accumulation step > 1 时,会出现梯度被覆盖或未 sync 的问题。

正确做法是:用torch.npu.amp.GradScaler+npu_sync显式控制:

scaler = torch.npu.amp.GradScaler() for step, batch in enumerate(train_dataloader): with torch.npu.amp.autocast(): loss = model(**batch).loss scaler.scale(loss).backward() if (step + 1) % accumulation_steps == 0: # 关键:在 step 前 sync 所有 stream torch.npu.synchronize() # 等待当前 batch 所有 kernel 完成 scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_none=True) # 再次 sync,确保 grad update 完成 torch.npu.synchronize()

torch.npu.synchronize()是昇腾特有 API,它等价于aclrtSynchronizeStream,确保当前 default stream 上所有 kernel 执行完毕。漏掉这一步,scaler.step()可能读到未写完的梯度,导致参数更新错误。我们实测过,不加synchronize()时,accumulation_steps=4 的训练,第 3 个 step 的 grad norm 是正常的 3.2,第 4 个 step 突然变成 0.001,loss 直接归零——因为 grad tensor 被前序未 sync 的 kernel 覆盖了。

4. 实战配置清单:昇腾310P3 上 LLaMA-3 7B 变长训练的最小可行配置

基于我们在 310P3(32GB 显存,CANN 7.0)上跑通 LLaMA-3 7B 的经验,整理出一份经过生产验证的最小可行配置清单。这不是理论参数,而是每一行都经过npu-smi和ascend-profiler实测的硬性要求。

4.1 环境与版本锁定

昇腾生态对版本极其敏感,以下组合是唯一验证通过的:

组件版本说明
CANN Toolkit7.0.RC1必须用 RC1,7.0 GA 版有 dynamic shape bug
PyTorch-NPU2.1.0+gitcann70rc1不能用 pip install torch,必须从昇腾官网下载 whl
Python3.9.193.10+ 会导致torch.compilefallback 到 CPU
GCC9.3.0升腾编译器链要求,用 11.x 会 link error

注意:pip install torch安装的 PyTorch 会覆盖torch_npu,必须先pip uninstall torch,再pip install torch_npu-2.1.0+gitcann70rc1-cp39-cp39-linux_x86_64.whl。我们曾因版本错配,浪费 3 天排查ACL_ERROR_NOT_FOUND。

4.2 torch.compile 配置详解

import torch import torch_npu # 必须在 import torch_npu 后立即设置 torch.npu.set_device(0) model = AutoModelForCausalLM.from_pretrained( "meta-llama/Meta-Llama-3-7B", torch_dtype=torch.float16, device_map="npu:0" ) # 关键配置项(全部必需) compiled_model = torch.compile( model, backend="ascend", options={ "variable_seq_lengths": True, # 启用 variable length "max_seq_length": 2048, # 内存安全上限,不能超 2048 "enable_graph_mode": True, # 强制启用 GE graph mode "dump_graph": False, # 生产环境关闭,debug 时设 True "enable_caching": True, # 开启 graph cache,避免重复 compile "cache_path": "/path/to/graph_cache" # cache 路径需有写权限 } )

max_seq_length=2048是 310P3 的硬性限制。实测max_seq_length=2560时,aclrtMalloc分配失败,日志显示ACL_ERROR_INVALID_SIZE。这不是模型限制,而是 310P3 的 memory controller 对 dynamic buffer 的最大地址空间限制。

enable_caching=True极其重要。昇腾的 graph compile 耗时很长(LLaMA-3 7B 首次 compile 12 分钟),没有 cache 会导致每个 epoch 重新 compile,训练效率归零。cache 文件约 1.2GB,需确保磁盘空间充足。

4.3 数据加载与预处理脚本模板

import torch from torch.utils.data import Dataset, DataLoader from torch.nn.utils.rnn import pad_sequence from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-7B") class DynamicSeqDataset(Dataset): def __init__(self, texts): self.texts = texts def __len__(self): return len(self.texts) def __getitem__(self, idx): text = self.texts[idx] tokens = tokenizer.encode(text, truncation=True, max_length=2048) return {"input_ids": torch.tensor(tokens, dtype=torch.long)} def collate_fn(batch): input_ids = pad_sequence( [item["input_ids"] for item in batch], batch_first=True, padding_value=0 ) # ✅ 关键:标记 seq_len 维度为 dynamic torch.npu.dynamo.mark_dynamic(input_ids, 1, -1) return {"input_ids": input_ids} # DataLoader 必须用 pin_memory=False,NPU 不支持 pinned memory train_dataloader = DataLoader( DynamicSeqDataset(train_texts), batch_size=4, collate_fn=collate_fn, num_workers=0, # 升腾不支持多进程 dataloader pin_memory=False )

num_workers=0是硬性要求。昇腾的npu_pin_memory未实现,设num_workers>0会导致RuntimeError: npu_pin_memory is not implemented。这意味着数据加载会成为瓶颈,必须用prefetch_factor=2和更大的batch_size补偿。

4.4 训练循环中的 NPU 专属优化

scaler = torch.npu.amp.GradScaler() optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) for epoch in range(10): for step, batch in enumerate(train_dataloader): # ✅ 每个 batch 前必须 sync,确保前序 kernel 完成 torch.npu.synchronize() with torch.npu.amp.autocast(): outputs = compiled_model(**batch) loss = outputs.loss scaler.scale(loss).backward() if (step + 1) % 4 == 0: # accumulation steps = 4 torch.npu.synchronize() # ✅ step 前 sync scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_none=True) torch.npu.synchronize() # ✅ step 后 sync

torch.npu.synchronize()出现三次,位置不能错。第一次在 batch 开头,防止前序 batch 的 kernel 干扰;第二、三次在 gradient accumulation 的边界,确保 grad update 原子性。少一次,loss 就会异常。

5. 常见故障排查:从日志定位到根因修复的完整路径

昇腾变长训练的报错,90% 都藏在 ACL 日志里,而不是 PyTorch traceback。我整理了 5 类最高频故障,每类都给出日志特征、根因分析和修复命令。

5.1 故障一:ACL_ERROR_INVALID_PARAM—— variable_seq_lengths 未生效

日志特征:

[ERROR] ACL: aclrtCreateStream failed, error code: ACL_ERROR_INVALID_PARAM [INFO] GE: Graph build failed, variable_seq_lengths is false but input has dynamic shape

根因:variable_seq_lengths=True未传入torch.compile,或传入但input_ids.shape[1]不是-1。

排查步骤:

  1. 检查torch.compileoptions 字典是否含"variable_seq_lengths": True;
  2. 在 dataloader 后加print(batch["input_ids"].shape),确认是torch.Size([4, -1]);
  3. 运行export ASCEND_SLOG_PRINT_TO_SCREEN=1,重新运行,看 GE 日志是否含variable_seq_lengths is true。

修复命令:

# 清除 graph cache,避免旧配置残留 rm -rf /path/to/graph_cache/* # 重启 python 进程,重新 compile

5.2 故障二:ACL_ERROR_NOT_FOUND—— CANN 版本不匹配

日志特征:

[ERROR] ACL: aclrtSetDevice failed, error code: ACL_ERROR_NOT_FOUND [ERROR] CANN: Failed to load libascendcl.so

根因:PyTorch-NPU 版本与 CANN Toolkit 版本不匹配,或LD_LIBRARY_PATH未指向 CANN lib。

排查步骤:

  1. 运行npu-smi info,确认 CANN 版本;
  2. 运行python -c "import torch_npu; print(torch_npu.__version__)",确认 PyTorch-NPU 版本;
  3. 运行echo $LD_LIBRARY_PATH | grep ascend,确认包含/usr/local/Ascend/ascend-toolkit/latest/lib64。

修复命令:

# 设置正确的 LD_LIBRARY_PATH export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/lib64:$LD_LIBRARY_PATH # 重新安装匹配版本的 PyTorch-NPU pip uninstall torch_npu -y pip install torch_npu-2.1.0+gitcann70rc1-cp39-cp39-linux_x86_64.whl

5.3 故障三:Loss NaN 或剧烈震荡 —— dynamic shape 破坏

日志特征:

[WARNING] GE: Dynamic shape inference failed, fallback to static shape [INFO] GE: Using static shape mode for graph execution

根因:模型 forward 中用了原生 PyTorch 操作(如x[:, :seq_len]),导致 dynamic shape 丢失。

排查步骤:

  1. 在 model forward 中每个 tensor 操作后加assert x.is_dynamic(), f"{x.shape} is not dynamic";
  2. 运行export ASCEND_GRAPH_DUMP=1,查看 dump 的 graph 是否含dynamic_shape=true属性。

修复命令:

# 将所有切片操作替换为 npu_slice # ❌ 错误 x = x[:, :seq_len] # ✅ 正确 x = torch.npu.npu_slice(x, [0, 0], [-1, seq_len])

5.4 故障四:显存 OOM —— max_seq_length 设置过大

日志特征:

[ERROR] ACL: aclrtMalloc failed, error code: ACL_ERROR_RESOURCE_ABNORMAL [ERROR] GE: Memory allocation failed for dynamic buffer

根因:max_seq_length超过 310P3 的 dynamic buffer 地址空间上限。

排查步骤:

  1. 运行npu-smi d -i 0,查看显存使用率;
  2. 尝试max_seq_length=1024,确认是否 OOM;
  3. 如果1024不 OOM,2048OOM,则确认是地址空间限制。

修复命令:

# 降低 max_seq_length,并调整 batch_size 补偿 compiled_model = torch.compile( model, backend="ascend", options={"max_seq_length": 1024, "variable_seq_lengths": True} ) # batch_size 从 4 提到 8

5.5 故障五:训练速度慢于 GPU —— dataloader 瓶颈

日志特征:

[INFO] Profiler: DataLoad time: 120ms, ModelForward time: 8ms

根因:num_workers>0导致 dataloader hang,或pin_memory=True触发 host-device copy。

排查步骤:

  1. 运行npu-smi d -i 0 -t 1,观察Util是否长期为 0,而DataLoad时间占比 >80%;
  2. 检查 dataloader 是否设num_workers=0和pin_memory=False。

修复命令:

# 确保 dataloader 配置正确 train_dataloader = DataLoader( dataset, batch_size=8, num_workers=0, pin_memory=False, prefetch_factor=2 # 提前加载下一个 batch )

6. 性能对比与成本权衡:为什么值得为昇腾折腾这套配置

最后说点实在的:折腾这套配置到底值不值?我拿 LLaMA-3 7B 在 310P3 和 A100 上做了 72 小时连续训练对比,结论很明确——不是为了“替代 GPU”,而是为了“在特定场景下获得更高性价比”。

指标昇腾310P3(32GB)A100(40GB)差异
单卡吞吐(tokens/sec)185212-12.7%
8卡多机训练扩展效率92%85%+7%
单卡功耗(W)180300-40%
每万 tokens 训练成本(¥)0.380.52-26.9%
部署延迟(ms)14.215.8-10.1%

看到没?单卡吞吐略低,但多机扩展效率更高、功耗更低、部署延迟更短。这是因为昇腾的 HCCL 通信库针对 variable_seq_lengths 做了深度优化:当 batch 内 sequence 长度不同时,HCCL 会自动压缩通信 payload,减少无效 token 传输。A100 的 NCCL 则必须 padding 到统一长度,带宽浪费严重。

我们实际业务中,用户 query 长度分布极不均匀(50% < 128 tokens,30% 128~512,20% > 512),用 A100 训练时,必须 padding 到 1024,有效 token 率仅 38%;而昇腾用 variable_seq_lengths,有效 token 率达 92%,通信带宽节省 57%。这就是为什么 8 卡扩展效率反而更高——不是硬件强,而是软件栈更贴合变长场景。

所以,如果你的业务符合以下任一条件,昇腾变长训练就是刚需:

  • 用户输入长度高度不均(客服对话、搜索 query、代码补全);
  • 需要低功耗边缘部署(车载、工控、终端);
  • 多机训练规模 > 32 卡,通信开销成为瓶颈;
  • 对推理延迟敏感,且 batch 内 query 长度差异大。

反之,如果你的训练数据全是固定长度(如机器翻译平行语料),那真没必要折腾variable_seq_lengths,老老实实用 static shape,昇腾一样快。

我在实际项目里总结出一条铁律:昇腾不是“另一个 CUDA”,它是“为变长而生的 NPU”。它的优势不在峰值算力,而在对 irregular computation 的原生支持。理解这一点,你才能避开那些“配置写了但没用”的坑,真正把昇腾的潜力榨出来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 21:21:02

MOS管为什么会炸?

MOS管为什么会炸&#xff1f; 从波形看懂几种常见失效原因 空载能启动&#xff0c;一加负载就炸&#xff1b;室温调试没问题&#xff0c;装进机箱运行一阵却烧了&#xff1b;PWM 明明留了死区&#xff0c;上下管仍然一起损坏。碰到这些现象&#xff0c;换一颗电流更大、耐压更…

作者头像 李华
网站建设 2026/9/28 21:20:23

Python第四周学习笔记

文章目录前言一、元组&#xff08;tuple&#xff09;1.元组特点2. 常用操作示例二、函数1. 什么是函数2. 分类三、变量作用域四、数据类型转换五、文件操作1. 打开模式2. 基础读写六、递归&#xff08;简单理解&#xff09;总结前言 在掌握了变量、分支 if、while 循环、break…

作者头像 李华
网站建设 2026/9/28 21:20:22

heapcreator(hitcontraning_lab13)

静态分析我们拿到题目之后先查看一下保护机制RELRO STACK CANARY NX PIE Partial RELRO Canary found NX enabled No PIEPartial RELRO → GOT 可写&#xff0c;能劫持 GOTNo PIE → 程序基址固定 0x400000&#xff0c;GOT 地址已知libc 2…

作者头像 李华
网站建设 2026/9/28 21:20:15

术语总表 ·《Zero to Python》

术语表规范&#xff1a;同一术语只在首现单元收录&#xff0c;后续单元写「见单元 N 术语表」&#xff1b;正文与代码里出现的新写法必须在首现单元有词条。三列固定&#xff1a;术语 / 一句话解释 / 首次出现。 维护方式&#xff1a;每单元写完&#xff0c;把该单元术语表追加…

作者头像 李华