|
| 1 | +# 共创者指引 |
| 2 | +> Move fast, break things. |
| 3 | +
|
| 4 | +### 一、环境准备 |
| 5 | +1. **Fork 仓库**: |
| 6 | + |
| 7 | + a. 访问 [https://github.com/PaddlePaddle/GraphNet](https://github.com/PaddlePaddle/GraphNet) |
| 8 | + |
| 9 | + b. 点击右上角的 **Fork** 按钮,将仓库复制到你的账户下。 |
| 10 | + |
| 11 | +2. **克隆仓库到本地**: |
| 12 | + |
| 13 | +```bash |
| 14 | +git clone https://github.com/你的用户名/GraphNet.git |
| 15 | +cd GraphNet |
| 16 | +``` |
| 17 | +3. **添加上游仓库引用**: |
| 18 | + |
| 19 | +```bash |
| 20 | +git remote add upstream https://github.com/PaddlePaddle/GraphNet.git |
| 21 | +``` |
| 22 | +4. **安装依赖** |
| 23 | + |
| 24 | +```bash |
| 25 | +pip install torch, torchvision |
| 26 | +``` |
| 27 | +5. **设置提取路径**: |
| 28 | + |
| 29 | +```bash |
| 30 | +export GRAPH_NET_EXTRACT_WORKSPACE=/home/yourname/graphnet_workspace |
| 31 | +``` |
| 32 | +### 二、编辑脚本 |
| 33 | +#### 1. 编辑自动抽取脚本 |
| 34 | +> 我们鼓励借助LLM Agent的能力,把本文档中的example、仓库中不同模型的example与extractor装饰器和promts一起输入,面向所需新的模型开发抽取脚本。 |
| 35 | +
|
| 36 | +核心流程示例: |
| 37 | +```python |
| 38 | +import torch |
| 39 | +from torchvision.models import get_model, get_model_weights, list_models # 或者其他模型库 |
| 40 | +from graph_net.torch.extractor import extract |
| 41 | + |
| 42 | +def run_model(name: str, device_str: str) -> None: |
| 43 | + """ |
| 44 | + 对指定模型执行计算图抽取流程并导出结果。 |
| 45 | +
|
| 46 | + Args: |
| 47 | + name (str): 模型名称(例如 'resnet50'、'vit_b_16'、'bert-base-uncased' 等)。 |
| 48 | + device_str (str): 运行设备标识('cpu' 或 'cuda:0' 等)。 |
| 49 | + """ |
| 50 | + device = torch.device(device_str) |
| 51 | + print(f"\nTesting model: {name} on {device_str}") |
| 52 | + |
| 53 | + # 1. 加载模型权重 |
| 54 | + weights = None |
| 55 | + try: |
| 56 | + w = get_model_weights(name) |
| 57 | + weights = w.DEFAULT |
| 58 | + except Exception: |
| 59 | + # 若模型库不支持权重自动加载,可留空或在此手动指定本地权重路径 |
| 60 | + pass |
| 61 | + |
| 62 | + # 2. 实例化模型 |
| 63 | + try: |
| 64 | + model = get_model(name, weights=weights) |
| 65 | + except Exception as e: |
| 66 | + print(f"[FAIL] {name}: instantiate model error - {e}") |
| 67 | + return |
| 68 | + |
| 69 | + # 3. 构造输入张量(默认适用于图像分类模型) |
| 70 | + cfg = getattr(model, "default_cfg", {}) |
| 71 | + C, H, W = cfg.get("input_size", (3, 224, 224)) |
| 72 | + input_data = torch.rand(1, C, H, W, device=device) |
| 73 | + |
| 74 | + # 4. 包装并抽取计算图 |
| 75 | + model = model.to(device).eval() |
| 76 | + wrapped = extract(name=name)(model).eval() |
| 77 | + try: |
| 78 | + with torch.no_grad(): |
| 79 | + wrapped(input_data) |
| 80 | + print(f"[OK] {name}") |
| 81 | + except Exception as e: |
| 82 | + print(f"[FAIL] {name}: extract error - {e}") |
| 83 | +``` |
| 84 | + |
| 85 | + |
| 86 | +a. **准备模型**:加载模型定义及权重。 |
| 87 | + |
| 88 | +b. **构造输入** |
| 89 | + |
| 90 | +> GraphNet的示例仅限于特定模型,在完成任务时可能需要改输入数据的构造。 |
| 91 | +
|
| 92 | +不同模型对输入格式要求不一致,需根据模型类型生成合适的 `input_data`: |
| 93 | + |
| 94 | + |
| 95 | +<table> |
| 96 | + <thead> |
| 97 | + <tr> |
| 98 | + <th>模型类型</th> |
| 99 | + <th>输入构造示例</th> |
| 100 | + </tr> |
| 101 | + </thead> |
| 102 | + <tbody> |
| 103 | + <tr> |
| 104 | + <td>图像分类 / CV</td> |
| 105 | + <td><code>torch.rand(1, C, H, W)</code>;如 ResNet、ViT 默认 <code>(3,224,224)</code></td> |
| 106 | + </tr> |
| 107 | + <tr> |
| 108 | + <td>视频模型</td> |
| 109 | + <td><code>torch.rand(1, C, T, H, W)</code>;如 R3D、MViT 中 <code>T=num_frames</code></td> |
| 110 | + </tr> |
| 111 | + <tr> |
| 112 | + <td>NLP 文本模型</td> |
| 113 | + <td> |
| 114 | + <pre><code class="language-python">tokenizer = AutoTokenizer.from_pretrained(model_name) |
| 115 | + inputs = tokenizer( |
| 116 | + text, |
| 117 | + return_tensors="pt", |
| 118 | + padding=True, |
| 119 | + truncation=True, |
| 120 | + max_length=512, |
| 121 | + ) |
| 122 | + input_data = {key: val.to(device) for key, val in inputs.items()}</code></pre> |
| 123 | + </td> |
| 124 | + </tr> |
| 125 | + <tr> |
| 126 | + <td>多输入 / 复杂</td> |
| 127 | + <td>根据模型<code>forward</code>签名构造,如同时输入图像特征和位置编码等时,将所有Tensor按顺序或命名打包成 <code>tuple</code>/<code>dict</code></td> |
| 128 | + </tr> |
| 129 | + </tbody> |
| 130 | +</table> |
| 131 | + |
| 132 | +> **提示**:如果不确定 `model.forward()` 需要哪些参数,可以先打印签名: |
| 133 | +```python |
| 134 | +import inspect |
| 135 | +print(inspect.signature(model.forward)) |
| 136 | +``` |
| 137 | + |
| 138 | + c. **使用装饰器** |
| 139 | + |
| 140 | +i. 推荐直接使用简洁的链式调用,例如上面示例中的`wrapped = extract(name=name)(model).eval()`。 |
| 141 | + |
| 142 | +ii. 如果需要使用外置的显式`@extract`装饰器,则需要装饰模型的`nn.Module`子类。 |
| 143 | + |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | +#### 2. 【可选】调整extractor |
| 148 | +在少数情况下,可能需要修改extract decorator从而适配特定模型。此时,请提前提起issue并**特别标注说明**,与我们一起规划该特性。 |
| 149 | + |
| 150 | +值得注意的是,这种操作更考验开发者的能力,同时我们也会对新的extractor代码质量及安全性进行人工Review。 |
| 151 | + |
| 152 | +同时,完成extractor的优化开发意味着可以适配新的一系列模型,从而使得您可以获得一大批的任务激励; |
| 153 | + |
| 154 | +如果新方法能适配的模型数量有限,则ROI较低,这也体现了“Eat the frog first”的原则。 |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | +#### 3. TroubleShot指南:借助 LLM 自动补全输入构造 |
| 159 | +> 示例代码仅覆盖部分常见模型,实际使用中可能需要根据具体模型调整输入张量的构造。 |
| 160 | +> 如果在抽取计算图过程中遇到错误,可以直接把错误日志和代码文件送给LLM Agent处理。 |
| 161 | +> 下方示例展示了一个Debug和输入生成的流程记录。 |
| 162 | +
|
| 163 | + |
| 164 | +在首次抽取计算图时,可能因 `input_data` 维度不匹配而导致失败。此时我们借助 LLM,根据日志提示补充或修正输入构造逻辑。 |
| 165 | + |
| 166 | +示例错误日志: |
| 167 | +```bash |
| 168 | +[FAIL] r2plus1d_18: Dynamo failed to run FX node with fake tensors: |
| 169 | + call_function <built-in method conv3d of type object at 0x7f14dbbd1f40>( |
| 170 | + *(FakeTensor(..., device='cuda:0', size=(1, s0, s1, s1)), |
| 171 | + Parameter(FakeTensor(..., device='cuda:0', size=(45, 3, 1, 7, 7), requires_grad=True)), |
| 172 | + None, (1, 2, 2), (0, 3, 3), (1, 1, 1), 1), |
| 173 | + **{} |
| 174 | + ): got RuntimeError( |
| 175 | + 'Given groups=1, weight of size [45, 3, 1, 7, 7], expected input[1, 1, s0, s1, s1] |
| 176 | + to have 3 channels, but got 1 channels instead' |
| 177 | + ) |
| 178 | +``` |
| 179 | +从日志可见,该模型在做 3D 卷积时,输入通道数(1)与权重通道数(3)不符。基于这一提示,向 LLM 请求补全如下输入逻辑,即为时序模型增加帧维度 `T`: |
| 180 | + |
| 181 | +```python |
| 182 | +if any(tok in name for tok in ("r2plus1d")): |
| 183 | + # 时序模型需按 (B, C, T, H, W) 构造输入 |
| 184 | + T = cfg.get("num_frames", 16) |
| 185 | + input_data = torch.rand(1, C, T, H, W, device=device) |
| 186 | +``` |
| 187 | +此时再次运行 `extract(name)(model)(input_data)`,即可成功抽取计算图。 |
| 188 | + |
| 189 | + |
| 190 | + |
| 191 | +#### 4. 常见问题 |
| 192 | + * **模型名称不在 **`list_models()`** 中**: |
| 193 | + * 自行从第三方库加载模型或手动实现 `get_model` 接口。 |
| 194 | + * 确保 `extract(name=name)` 中的 `name` 与导出文件名一致。 |
| 195 | + |
| 196 | + * **输入维度不匹配**: |
| 197 | + * 捕获异常后,打印模型 `cfg` 或 `forward` 签名进行对比。 |
| 198 | + * 若模型有动态输入长度(如可变 sequence length),可使用临时最大长度测试。 |
| 199 | + |
| 200 | + * **显存不足 / 速度慢**: |
| 201 | + * 尝试先在 CPU 上小 batch 测试; |
| 202 | + * 对于超大模型,建议分阶段抽取或使用更小 `input_data`。 |
| 203 | + |
| 204 | + |
| 205 | + |
| 206 | + |
| 207 | +#### 5. 进阶用法 |
| 208 | + * **多输入/多输出模型**: |
| 209 | + * `input_data` 可为 `tuple` 或 `dict`,并在 `wrapped(...)` 时一并传入。 |
| 210 | + |
| 211 | + * **分布式 & 并行抽图**: |
| 212 | + * 可结合 `multiprocessing` 或 `torch.multiprocessing`,多进程并行抽取多模型。 |
| 213 | + |
| 214 | + * **自定义 hooks**: |
| 215 | + * 如果需要在特定层插入钩子,可在 `wrapped = extract(...)(model, hooks=...)` 中传入自定义函数。 |
| 216 | + |
| 217 | + |
| 218 | + |
| 219 | +### 三、抽取计算图 |
| 220 | +1. **extract 抽取** |
| 221 | + |
| 222 | +运行集成了`@graph_net.torch.extract`或`@graph_net.paddle.extract`的自动提取脚本,例如: |
| 223 | + |
| 224 | +```bash |
| 225 | +# Extract the ResNet‑18 computation graph |
| 226 | +python -m graph_net.test.vision_model_test |
| 227 | +``` |
| 228 | +按照预期,应当在您的`$GRAPH_NET_EXTRACT_WORKSPACE`目录下记录所抽取的文件。 |
| 229 | + |
| 230 | + |
| 231 | + |
| 232 | +2. **validate 自查** |
| 233 | + |
| 234 | +```bash |
| 235 | +python -m graph_net.torch.validate --model-path $GRAPH_NET_EXTRACT_WORKSPACE/model_name |
| 236 | +``` |
| 237 | +`validate` 验证您刚刚抽取的计算图符合Dataset Construction Constraints,如果结果为Success,则可以继续。 |
| 238 | + |
| 239 | + |
| 240 | + |
| 241 | +### **四、提交**计算图 |
| 242 | +1. **配置贡献者用户名和email** |
| 243 | + |
| 244 | +```bash |
| 245 | +python -m graph_net.config \ |
| 246 | + --global \ |
| 247 | + --username "john_doe" \ |
| 248 | + |
| 249 | +``` |
| 250 | +2. **打包计算图** |
| 251 | + |
| 252 | +```bash |
| 253 | +python -m graph_net.pack --output /path/to/output.zip --clear-after-pack True |
| 254 | +``` |
| 255 | +该API的功能为: |
| 256 | + |
| 257 | + a. 打包`$GRAPH_NET_EXTRACT_WORKSPACE`下的所有文件到`/path/to/output.zip` (可以设置到`GraphNet/samples`) |
| 258 | + |
| 259 | + b. 若`--clear-after-pack`为`True`,则打包后清空`$GRAPH_NET_EXTRACT_WORKSPACE` |
| 260 | + |
| 261 | +请注意,如果有第三方算子,需要贡献者自行打包到计算图压缩包内。目前没有特别规定存放的目录结构,但只要通过了validate环节,就可以达到验收标准。 |
| 262 | + |
| 263 | +3. **提交修改** |
| 264 | + |
| 265 | +移动上一步打包完成的计算图压缩包到**samples**目录,然后提交。 |
| 266 | +```bash |
| 267 | +git add <计算图压缩包> |
| 268 | +git commit -m "描述" |
| 269 | +``` |
| 270 | +4. **推送分支到远程**(你的 Fork 仓库) |
| 271 | + |
| 272 | +```bash |
| 273 | +git push origin feature/your-branch-name |
| 274 | +``` |
| 275 | +5. **提交 Pull Request** |
| 276 | + |
| 277 | +> **注意**:为方便管理,每个PR应遵守Single Responsibility Principle (SRP)原则,**仅新增单一份计算图、或聚焦于单一功能改进**,避免将多个修改混合提交。例如,如果您修改了抓取方法,然后为支持某类模型收集了数据,那么其中每份单个模型的计算图、修改的新一份抓取方法,都应打开为独立的PR。 |
| 278 | +
|
| 279 | + 1. 访问你的 Fork 仓库页面(`https://github.com/你的用户名/GraphNet`)。 |
| 280 | + |
| 281 | + 2. 页面会提示 **Compare & Pull Request**,点击它。 |
| 282 | + |
| 283 | + 3. 使用以下模版填写: |
| 284 | + |
| 285 | +**// ------- PR 标题 --------** |
| 286 | + |
| 287 | +`[Type (New Sample | Feature Enhancement | Bug Fix | Other)] Brief Description` |
| 288 | + |
| 289 | +`eg. [Feature Enhancement] Support Bert Model Family on Pytorch` |
| 290 | + |
| 291 | +`eg. [New Sample] Add "distilbert-base-uncased" Model Computational Graph` |
| 292 | + |
| 293 | +**// ------- PR 内容 --------** |
| 294 | + |
| 295 | +```markdown |
| 296 | +Model: eg. distilbert-base-uncased |
| 297 | +Framework: eg. Pytorch/Paddle |
| 298 | +Dependency: eg. torchvision, transformers |
| 299 | +Content: Description |
| 300 | +``` |
| 301 | + |
| 302 | + 4. 点击 **Create Pull Request**。 |
| 303 | + |
| 304 | + 5. GraphNet团队会在机器人辅助下审查并合入PR。 |
| 305 | + |
| 306 | +> 其它信息及未明确的规范,可参照 [Paddle社区统一的代码贡献流程](https://www.paddlepaddle.org.cn/documentation/docs/zh/dev_guides/code_contributing_path_cn.html) |
0 commit comments