chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
@@ -0,0 +1,13 @@
# Copyright (c) 2022 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.
@@ -0,0 +1,211 @@
# Copyright (c) 2022 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.
import os
import sys
from paddle.distributed.launch.utils.process_context import ProcessContext
from .status import Status
class Container:
'''
TODO(kuizhiqing) A container can be run by process/thread or just a callable function
'''
def __init__(self, entrypoint=[], rank=-1, env={}, overwrite_log=False):
self._entrypoint = entrypoint
self._rank = rank
self._out = None
self._err = None
self._env = env
self._proc = None
self._retry: int = 3
self._grace_period = 10
self._log_handler = None
self._shell = False
self.log_mode = 'w' if overwrite_log else 'a'
@property
def env(self):
return self._env
@property
def entrypoint(self):
return self._entrypoint
@entrypoint.setter
def entrypoint(self, entry):
self._entrypoint = entry
@property
def rank(self):
return self._rank
@rank.setter
def rank(self, r):
self._rank = r
@property
def outfile(self):
return self._out
@outfile.setter
def outfile(self, out):
self._out = out
@property
def errfile(self):
return self._err
@errfile.setter
def errfile(self, err):
self._err = err
@property
def shell(self):
return self._shell
@shell.setter
def shell(self, shell):
self._shell = shell
def update_env(self, env={}, **kwargs):
env = {k: v for k, v in env.items() if isinstance(v, str)}
self._env.update(env)
kwargs = {k: v for k, v in kwargs.items() if isinstance(v, str)}
self._env.update(kwargs)
def _validate_env(self):
for k, v in self._env.items():
assert isinstance(k, str) and isinstance(v, str), (
f'env {k}:{v} must be str'
)
def _get_fd(self, pth):
if not pth:
return None
try:
d = os.path.dirname(pth)
if not os.path.isdir(d):
os.makedirs(d, exist_ok=True)
return open(pth, self.log_mode)
except:
return None
def start(self):
if self._proc and self._proc.alive():
return True
self._validate_env()
self._stdout = self._get_fd(self._out) or sys.stdout
if self._out == self._err:
self._stderr = self._stdout
elif self._err:
self._stderr = self._get_fd(self._err) or sys.stderr
if self._out and not self._log_handler:
self._log_handler = open(self._out)
self._log_handler.seek(0, 2)
self._log_start_offset = self._log_handler.tell()
self._proc = ProcessContext(
self._entrypoint,
env=self._env,
out=self._stdout,
err=self._stderr,
shell=self._shell,
)
self._proc.start()
def terminate(self, force=False):
if self._log_handler:
self._log_handler.close()
self._log_handler = None
if self._proc and self._proc.alive():
return self._proc.terminate(force)
def wait(self, timeout=None):
try:
self._proc.wait(timeout)
return True
except Exception:
return False
@property
def exit_code(self):
return self._proc.exit_code() if self._proc else -1
@property
def status(self):
if not self._proc:
return Status.UNINIT
if self._proc.alive():
return Status.RUNNING
elif self._proc.exit_code() == 0:
return Status.COMPLETED
else:
return Status.FAILED
def __str__(self):
need_print = os.environ.get('FLAGS_print_launcher_env', 'false').lower()
if need_print == 'true' or need_print == '1':
return f'Container rank {self._rank} status {self.status} cmd {self._entrypoint} code {self.exit_code} log {self.errfile} \nenv {self._env}'
return f'Container rank {self._rank} status {self.status} cmd {self._entrypoint} code {self.exit_code} log {self.errfile}'
def logs(self, fn=None, offset=0, whence=1, limit=1000):
if not self._log_handler:
return
if fn is None:
fn = sys.stdout
try:
if offset != 0 or whence != 1:
if whence == 0 and offset < self._log_start_offset:
offset = self._log_start_offset
self._log_handler.seek(offset, whence)
for _ in range(limit):
line = self._log_handler.readline()
if not line:
return False
fn.write(line)
return True
except:
return
def tail(self, length=3000):
if not self._log_handler:
return
try:
self._log_handler.seek(0, 2)
ed = self._log_handler.tell()
except:
pass
if ed > length:
self.logs(offset=ed - length, whence=0)
else:
self.logs(offset=0, whence=0)
@@ -0,0 +1,81 @@
# Copyright (c) 2022 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.
class JobMode:
COLLECTIVE = 'collective'
PS = 'ps'
HETER = 'heter'
class Job:
def __init__(self, jid='default', mode=JobMode.COLLECTIVE, nnodes="1"):
self._mode = mode
self._id = jid
self._replicas = 0
self._replicas_min = self._replicas
self._replicas_max = self._replicas
self._elastic = False
self.set_replicas(str(nnodes))
def __str__(self):
return f"Job: {self.id}, mode {self.mode}, replicas {self._replicas}[{self._replicas_min}:{self._replicas_max}], elastic {self.elastic}"
@property
def mode(self):
return self._mode
@property
def id(self):
return self._id
@property
def elastic(self):
return self._elastic
@property
def replicas(self):
return self._replicas
@property
def replicas_min(self):
return self._replicas_min
@property
def replicas_max(self):
return self._replicas_max
@replicas.setter
def replicas(self, replicas):
self._replicas = replicas
def set_replicas(self, nnodes: str):
np = str(nnodes) if nnodes else '1'
if ':' in np:
nps = np.split(':')
self._replicas_min, self._replicas_max = int(nps[0]), int(nps[1])
self._replicas = self._replicas_max # default to max
self._elastic = True
else:
self._replicas = int(np)
self._replicas_min, self._replicas_max = (
self._replicas,
self._replicas,
)
self._elastic = False
+212
View File
@@ -0,0 +1,212 @@
# Copyright (c) 2022 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 annotations
import random
import time
from typing import TYPE_CHECKING
from .status import Status
if TYPE_CHECKING:
from .container import Container
class PodSpec:
def __init__(self):
self._name = ''.join(
random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(6)
)
# by controller
self._init_containers: list[Container] = []
self._containers: list[Container] = []
# self.resource: Resource = None
# self.status: Status = None
self._rank = -1
self._init_timeout = None
self._restart = -1
self._replicas = 0 # number of containers
self._exit_code = 0
class Pod(PodSpec):
def __init__(self):
super().__init__()
def __str__(self):
return (
f"Pod: {self.name}, replicas {self.replicas}, status {self.status}"
)
def failed_container(self):
cs = []
for c in self._containers:
if c.status == Status.FAILED:
cs.append(c)
return cs
@property
def name(self):
return self._name
@property
def replicas(self):
return self._replicas
@replicas.setter
def replicas(self, r):
self._replicas = max(r, 1)
@property
def rank(self):
return self._rank
@rank.setter
def rank(self, r):
self._rank = r
@property
def restart(self):
return self._restart
@property
def containers(self):
return self._containers
def add_container(self, c):
c.rank = len(self._containers)
self._containers.append(c)
@property
def init_containers(self):
return self._init_containers
def add_init_container(self, c):
c.rank = len(self._init_containers)
self._init_containers.append(c)
@property
def exit_code(self):
for c in self._containers:
if c.exit_code != 0:
return c.exit_code
return 0
def deploy(self):
# init container should stop before run containers
for i in self._init_containers:
i.start()
i.wait(self._init_timeout)
for c in self._containers:
c.start()
self._restart += 1
def stop(self, sigint=15, timeout=None):
for c in self._containers:
if isinstance(sigint, int) and timeout is None:
c.send_signal(sigint)
else:
c.terminate()
if isinstance(timeout, int):
if not self.join(timeout):
for c in self._containers:
c.terminate(force=True)
return False
else:
return True
def join(self, timeout=None):
for c in self._containers:
if not c.wait(timeout):
return False
return True
@property
def status(self):
if self.is_failed():
return Status.FAILED
if self.is_completed():
return Status.COMPLETED
if self.is_running():
return Status.RUNNING
return Status.READY
def reset(self):
self._init_containers = []
self._containers = []
def is_failed(self):
for c in self._containers:
if c.status == Status.FAILED:
return True
return False
def is_completed(self):
for c in self._containers:
if c.status != Status.COMPLETED:
return False
return True
def is_running(self):
for c in self._containers:
if c.status != Status.RUNNING:
return False
return True
def logs(self, idx=None):
if idx is None:
if len(self._containers) > 0:
self._containers[0].logs()
if len(self._init_containers) > 0:
self._init_containers[0].logs()
else:
self._containers[idx].logs()
def tail(self, idx=None):
if idx is None:
self._containers[0].tail()
else:
self._containers[idx].tail()
def watch(
self,
all_list=[Status.COMPLETED],
any_list=[Status.FAILED],
interval=1,
timeout=-1,
):
'''
watch return if any container status in any_list
or all container status in all_list
'''
end = time.time() + timeout
while timeout < 0 or time.time() < end:
for c in self._init_containers + self._containers:
if c.status in any_list:
return c.status
s = [c.status for c in self._init_containers + self._containers]
if len(set(s)) == 1 and s[0] in all_list:
return s[0]
time.sleep(interval)
@@ -0,0 +1,24 @@
# Copyright (c) 2022 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.
class Status:
UNINIT = "uninit"
READY = "ready"
RUNNING = "running"
FAILED = "failed"
TERMINATING = "terminating"
RESTARTING = "restarting"
UNKNOWN = "unknown"
COMPLETED = "completed"