Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions swe-paddle/tasks/PaddlePaddle__Paddle-18687/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# PaddlePaddle__Paddle-18687

This directory converts Paddle PR #18687 into a SWE-Paddle community task candidate.

## Source

| Field | Value |
| --- | --- |
| Repo | `PaddlePaddle/Paddle` |
| PR | [#18687](https://github.com/PaddlePaddle/Paddle/pull/18687) |
| PR title | `add parameter server launch` |
| Base commit | `d07ad4c6059db28c5f384a25190385742d9ba718` |
| Merged at | `2019-07-22T14:11:50Z` |
| Task type | `feature_enhancement` |
| Resource | CPU |

## Summary

Add a local parameter-server launcher that starts the requested server and worker processes with consistent roles, endpoints, identifiers, and training-script arguments.

## Why This Is A Good SWE-Paddle Candidate

- The change exercises command parsing, environment construction, role assignment, process orchestration, and failure propagation as one focused feature.
- Its 151-line Python production change is substantial while remaining isolated to one launcher file.
- Deterministic CPU tests can validate the process contract without starting training, loading model weights, or requiring external services.
- A P2P case protects the existing collective launcher's training-argument forwarding behavior.

## Files

- `proposal.md`: candidate proposal for maintainer triage.
- `instruction.md`: self-contained problem statement for the coding agent.
- `solution/code.patch`: gold patch from the merged PR.
- `tests/test.patch`: behavior tests exposing the target feature.
- `tests/test.sh`: minimal target test command.
- `environment/README.md`: environment notes for reproduction.
- `README.md`: task overview and verification entrypoint.

## Verification

```bash
bash tests/test.sh
```

Expected behavior: applying `tests/test.patch` to `base_commit` should keep the existing launcher regression case passing while the parameter-server cases fail; applying both `tests/test.patch` and `solution/code.patch` should pass all target tests.
27 changes: 27 additions & 0 deletions swe-paddle/tasks/PaddlePaddle__Paddle-18687/environment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Environment Notes

This candidate is part of the SWE-Paddle community task set.

## Expected Environment

- Repository: `PaddlePaddle/Paddle`
- Base commit: `d07ad4c6059db28c5f384a25190385742d9ba718`
- Resource: CPU
- GPU required: no
- Build path: no source build is required; the tests execute checkout Python control flow with controlled subprocess doubles.

## Run Order

1. Check out `PaddlePaddle/Paddle` at the base commit.
2. Apply `tests/test.patch`.
3. Run `bash tests/test.sh`; the target behavior should fail before the fix.
4. Apply `solution/code.patch`.
5. Run `bash tests/test.sh` again; the target behavior should pass after the gold patch.

## Minimal Test Command

```bash
bash tests/test.sh
```

The verifier is responsible for deriving stable F2P and P2P node IDs from repeated runs.
19 changes: 19 additions & 0 deletions swe-paddle/tasks/PaddlePaddle__Paddle-18687/instruction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 为参数服务器分布式训练增加统一的本地启动入口

## 详细描述

在本地调试参数服务器(Parameter Server)分布式训练时,需要同时启动 server 和 worker 进程,并为各进程配置角色、编号和通信信息。随着进程数量增加,手动维护这些配置容易出现不一致,也不便于统一传递训练脚本参数和处理子进程异常。

需要提供一个与现有分布式启动方式一致的命令入口。开发者只需指定 server 数量、worker 数量、起始端口和训练脚本,启动器即可一次拉起完整任务,并将训练脚本后的参数传递给各个子进程。

## 验收说明

- 按指定数量启动 server 和 worker,并为同一角色分配不重复的编号;所有子进程应拿到一致的 server 地址和 worker 总数。
- 训练脚本参数应原样传递,启动器应等待所有子进程结束,并在子进程以非零状态退出时报告失败。
- 子进程环境不应继承会干扰本地通信的代理设置,现有 collective launch 的参数转发行为保持不变。

## 技术要求

- 熟悉 Python 子进程和环境变量处理。
- 熟悉 PaddlePaddle 分布式 launch 流程。
- 了解参数服务器训练中的 server、worker 与 endpoint 配置。
55 changes: 55 additions & 0 deletions swe-paddle/tasks/PaddlePaddle__Paddle-18687/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Task Proposal: PaddlePaddle__Paddle-18687

## 1. 来源信息

- Instance ID:`PaddlePaddle__Paddle-18687`
- PR 链接:https://github.com/PaddlePaddle/Paddle/pull/18687
- PR 标题:`add parameter server launch`
- `base_commit`:`d07ad4c6059db28c5f384a25190385742d9ba718`
- merged 时间:`2019-07-22T14:11:50Z`
- 你的身份:熟悉该模块的 contributor
- 后续联系人:TBD

## 2. 问题一句话

为本地参数服务器训练提供统一启动入口,根据 server/worker 数量生成一致的进程环境并管理完整任务生命周期。

## 3. 为什么适合作为 SWE-Paddle 样本

- **真实性**:来源 PR 解决本地启动参数服务器任务时需要手工组织多个进程的问题。
- **代表性**:覆盖命令行解析、角色环境生成、endpoint 编排、子进程启动和失败传播。
- **边界清楚**:production change 仅新增一个 Python launcher,不涉及算子、模型权重或 GPU 执行。
- **非平凡性**:需要同时保证两类角色的数量、编号、共享配置、参数转发与进程退出语义。
- **环境友好性**:测试通过 controlled `Popen` doubles 验证真实控制流,可在 CPU 环境稳定运行。

## 4. 任务类型和标签

- 任务类型:`feature_enhancement`
- 执行后端:`cpu`
- 设备范围:`cpu_only`
- 模块标签:`[distributed, parameter_server, launcher, multiprocessing]`

## 5. 验证思路

- 目标测试命令:`bash tests/test.sh`
- 目标测试文件:`test/swe_paddle/test_pr18687_launch_ps.py`
- 修复前预期:现有 collective launcher 的参数转发测试通过;参数服务器 launcher 不存在,因此参数解析、角色编排和失败传播用例失败。
- 修复后预期:参数服务器命令行、server/worker 环境、命令参数、等待行为及非零退出码传播全部通过。
- P2P 候选:现有 `paddle.distributed.launch` 仍能把训练脚本及其余参数完整解析出来。

## 6. 环境与资源

- 资源需求:CPU
- Paddle 来源:`PaddlePaddle/Paddle` source checkout at `base_commit`
- 是否能提供 Docker:暂无
- patch 类型:Python-only
- 环境建议:无需 source build;使用 Python、pytest 和 controlled subprocess doubles 即可运行。
- 最小测试命令:`bash tests/test.sh`
- 是否有 oracle 日志:由 SWE-Paddle verifier 结果另行维护

## 7. 风险自查

- 泄露风险:instruction 只描述启动器的外部行为,不给出 Gold patch 的具体代码结构。
- 环境风险:不启动真实训练进程,不依赖 Paddle wheel、网络、外部服务或 GPU。
- flaky 风险:进程行为由确定性的 doubles 记录,不依赖端口竞争和真实多进程时序。
- 拆分风险:来源 PR 只有一个 production file,所有验证围绕同一个参数服务器启动入口。
157 changes: 157 additions & 0 deletions swe-paddle/tasks/PaddlePaddle__Paddle-18687/solution/code.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
diff --git a/python/paddle/distributed/launch_ps.py b/python/paddle/distributed/launch_ps.py
new file mode 100644
index 0000000000000000000000000000000000000000..ded2e49c3e63638710d74322afef1ed12ff53c6c
--- /dev/null
+++ b/python/paddle/distributed/launch_ps.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+from __future__ import unicode_literals
+import subprocess
+import sys
+import os
+import copy
+from argparse import ArgumentParser, REMAINDER
+
+
+def parse_args():
+ # Optional arguments for the launch helper
+ parser = ArgumentParser(description="Distributed training")
+ parser.add_argument(
+ "--cluster_node_ips",
+ type=str,
+ default="127.0.0.1",
+ help="Paddle cluster nodes ips, such as 192.168.0.16,192.168.0.17..")
+
+ parser.add_argument(
+ "--node_ip",
+ type=str,
+ default="127.0.0.1",
+ help="The current node ip. ")
+
+ parser.add_argument(
+ "--start_port",
+ type=int,
+ default=6170,
+ help="The trainer's start port on a single node")
+
+ parser.add_argument(
+ "--print_config",
+ type=bool,
+ default=True,
+ help="Print the config or not")
+
+ parser.add_argument(
+ "--worker_num", type=int, default=2, help="number of workers")
+
+ parser.add_argument(
+ "--server_num", type=int, default=2, help="number of servers")
+
+ parser.add_argument(
+ "--log_dir",
+ default="logs",
+ type=str,
+ help="The path for each process's log.If it's not setted, the log will printed to default pipe."
+ )
+
+ # positional
+ parser.add_argument(
+ "training_script",
+ type=str,
+ help="The full path to the single GPU training "
+ "program/script to be launched in parallel, "
+ "followed by all the arguments for the "
+ "training script")
+
+ # rest from the training program
+ parser.add_argument('training_script_args', nargs=REMAINDER)
+ return parser.parse_args()
+
+
+def start_procs(args):
+ worker_num = args.worker_num
+ server_num = args.server_num
+ start_port = args.start_port
+ default_env = os.environ.copy()
+ current_env = copy.copy(default_env)
+ current_env.pop("http_proxy", None)
+ current_env.pop("https_proxy", None)
+ procs = []
+ cmds = []
+ log_fns = []
+ ports = range(start_port, start_port + server_num, 1)
+ endpoints = ",".join(["127.0.0.1:" + str(x) for x in ports])
+ for i in range(server_num):
+ current_env.update({
+ "TRAINER_NUM": str(worker_num),
+ "CURRENT_ID": str(i),
+ "ENDPOINTS": endpoints,
+ "TRAINING_ROLE": "PSERVER"
+ })
+ cmd = [sys.executable, "-u", args.training_script
+ ] + args.training_script_args
+ cmds.append(cmd)
+ print(cmd)
+ if args.log_dir is not None:
+ os.system("mkdir -p {}".format(args.log_dir))
+ fn = open("%s/serverlog.%d" % (args.log_dir, i), "w")
+ log_fns.append(fn)
+ proc = subprocess.Popen(cmd, env=current_env, stdout=fn, stderr=fn)
+ else:
+ proc = subprocess.Popen(cmd, env=current_env)
+ procs.append(proc)
+
+ for i in range(worker_num):
+ current_env.update({
+ "ENDPOINTS": endpoints,
+ "TRAINER_NUM": str(worker_num),
+ "TRAINING_ROLE": "TRAINER",
+ "CURRENT_ID": str(i)
+ })
+ cmd = [sys.executable, "-u", args.training_script
+ ] + args.training_script_args
+ print(cmd)
+ cmds.append(cmd)
+ if args.log_dir is not None:
+ os.system("mkdir -p {}".format(args.log_dir))
+ fn = open("%s/workerlog.%d" % (args.log_dir, i), "w")
+ log_fns.append(fn)
+ proc = subprocess.Popen(cmd, env=current_env, stdout=fn, stderr=fn)
+ else:
+ proc = subprocess.Popen(cmd, env=current_env)
+ procs.append(proc)
+
+ for i in range(0, len(procs)):
+ proc = procs[i]
+
+ proc.wait()
+ if len(log_fns) > 0:
+ log_fns[i].close()
+
+ if proc.returncode != 0:
+ raise subprocess.CalledProcessError(
+ returncode=procs[i].returncode, cmd=cmds[i])
+
+
+def launch():
+ args = parse_args()
+ if args.print_config:
+ start_procs(args)
+
+
+# server num, worker num
+if __name__ == "__main__":
+ launch()
Loading