forked from luopingyi/xc_agent_platform_demo
Compare commits
1 Commits
v0.2.9-liv
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b14a4d1ee |
@@ -19,6 +19,8 @@ ENV EXTERNAL_SERVICE_TOKEN=4c44b92223c64bca9ed67701ceb2f4e8
|
|||||||
ENV XC_TOKEN=4c44b92223c64bca9ed67701ceb2f4e8
|
ENV XC_TOKEN=4c44b92223c64bca9ed67701ceb2f4e8
|
||||||
ENV XC_TOKEN_FILE=/app/secrets/xc_token
|
ENV XC_TOKEN_FILE=/app/secrets/xc_token
|
||||||
ENV ALL_TASKS_FOOTHOLD=1
|
ENV ALL_TASKS_FOOTHOLD=1
|
||||||
|
ENV LIVE_MAX_SUBMITS_PER_CYCLE=6
|
||||||
|
ENV LIVE_MAX_ATTEMPTS_PER_CYCLE=30
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ Tag `v0.2.3-live-token` 内置个人凭证,风险与停交说明见 [token bak
|
|||||||
|
|
||||||
Victor 的确认已内置,`LIVE_CONFIRM` 仍接受但不再必需。每次 task/add 前,
|
Victor 的确认已内置,`LIVE_CONFIRM` 仍接受但不再必需。每次 task/add 前,
|
||||||
通过 Xc-Token 全量分页读取 onlyMine task/page;非终态计入队列,读取失败停交。
|
通过 Xc-Token 全量分页读取 onlyMine task/page;非终态计入队列,读取失败停交。
|
||||||
`LIVE_QUEUE_THRESHOLD=95`(严格小于),`LIVE_MAX_SUBMITS_PER_CYCLE=3`,
|
`LIVE_QUEUE_THRESHOLD=95`(严格小于),`LIVE_MAX_SUBMITS_PER_CYCLE=6`(每小时 6 个;`LIVE_MAX_ATTEMPTS_PER_CYCLE=30`,60027/60028 及 code≠0 不计名额),
|
||||||
`LIVE_CYCLE_SECONDS=3600`(最小 60 秒)。环境队列提示仅可额外阻塞。
|
`LIVE_CYCLE_SECONDS=3600`(最小 60 秒)。环境队列提示仅可额外阻塞。
|
||||||
候选来自 `PENDING_SUBMIT_MD` / `PENDING_SUBMIT_TXT`,否则使用
|
候选来自 `PENDING_SUBMIT_MD` / `PENDING_SUBMIT_TXT`,否则使用
|
||||||
`src/data/candidates.json`(随镜像内置);A verified_reuse → 其他 A → B。
|
`src/data/candidates.json`(随镜像内置);A verified_reuse → 其他 A → B。
|
||||||
禁提、重复、GPU 未映射、占位符候选跳过。按 try_next_gpus 顺序选择首个已映射且未提交的模型/GPU 对。去重合并远端全部状态的 model/GPU、内置 `src/data/submitted-ledger.json` 和进程内提交记录;永久拒绝对内置于 `src/data/reject-marks.json`,不依赖容器文件。A 允许同模型换 GPU;do-not-resubmit 模型跨 GPU 全禁。服务启动记录 remote/baked/marks 数量;每次提交前刷新远端,失败则 hold `ledger_rebuild_failed`。
|
禁提、重复、GPU 未映射、占位符候选跳过。按 try_next_gpus 顺序选择首个已映射且未提交的模型/GPU 对。去重合并远端全部状态的 model/GPU、内置 `src/data/submitted-ledger.json` 和进程内提交记录;永久拒绝对内置于 `src/data/reject-marks.json`,不依赖容器文件。A 允许同模型换 GPU;do-not-resubmit 模型跨 GPU 全禁。服务启动记录 remote/baked/marks 数量;每次提交前刷新远端,失败则 hold `ledger_rebuild_failed`。
|
||||||
只读平台预检默认开启(`LIVE_PRECHECK=0` 关闭),每周期最多 `LIVE_MAX_PRECHECKS_PER_CYCLE=30` 次;预检失败仍由 task/add 判定。用 `python3 scripts/sync_reject_marks.py <log.txt>` 合并永久拒绝日志到 baked marks,重建镜像后保留;支持 `--dry-run`、`--marks` 和 `--date`。
|
只读平台预检默认开启(`LIVE_PRECHECK=0` 关闭),每周期最多 `LIVE_MAX_PRECHECKS_PER_CYCLE=60` 次;预检失败仍由 task/add 判定。用 `python3 scripts/sync_reject_marks.py <log.txt>` 合并永久拒绝日志到 baked marks,重建镜像后保留;支持 `--dry-run`、`--marks` 和 `--date`。
|
||||||
凭证优先级:`EXTERNAL_SERVICE_TOKEN` → `XC_TOKEN_FILE` → `XC_TOKEN` → `MODELHUB_XC_TOKEN`;跳过空值和占位符。每周期只记录 source 环境变量名,使用 `Xc-Token` 请求头,不记录凭证值。
|
凭证优先级:`EXTERNAL_SERVICE_TOKEN` → `XC_TOKEN_FILE` → `XC_TOKEN` → `MODELHUB_XC_TOKEN`;跳过空值和占位符。每周期只记录 source 环境变量名,使用 `Xc-Token` 请求头,不记录凭证值。
|
||||||
离线再生成:`python3 scripts/bake_candidates.py`、`python3 scripts/bake_ledger.py`,读取 `/workspace/modelhub-adapt` 本地文件;候选 342(A 46、B 296),ledger 206 对及 28 个模型禁提项。
|
离线再生成:`python3 scripts/bake_candidates.py`、`python3 scripts/bake_ledger.py`,读取 `/workspace/modelhub-adapt` 本地文件;候选 342(A 46、B 296),ledger 206 对及 28 个模型禁提项。
|
||||||
配置优先 build-config,失败使用 sample 并记录来源及原因。token 缺失只 hold。
|
配置优先 build-config,失败使用 sample 并记录来源及原因。token 缺失只 hold。
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ Victor 确认已内置,`LIVE_CONFIRM` 仍接受但不必需。
|
|||||||
`LIVE_QUEUE_THRESHOLD` 默认 95,只有完整分页的 onlyMine task/page 非终态数严格小于阈值才提交。
|
`LIVE_QUEUE_THRESHOLD` 默认 95,只有完整分页的 onlyMine task/page 非终态数严格小于阈值才提交。
|
||||||
缺凭证、HTTP/平台错误、JSON 错误、分页不完整或无效配置均停交。
|
缺凭证、HTTP/平台错误、JSON 错误、分页不完整或无效配置均停交。
|
||||||
ASYNC_QUEUE_DEPTH / QUEUE_SNAPSHOT_JSON 仅为额外阻塞提示。
|
ASYNC_QUEUE_DEPTH / QUEUE_SNAPSHOT_JSON 仅为额外阻塞提示。
|
||||||
每周期最多 `LIVE_MAX_SUBMITS_PER_CYCLE`(默认 3)次;周期
|
每周期最多 `LIVE_MAX_SUBMITS_PER_CYCLE`(默认 6,即每小时 6 个;`LIVE_MAX_ATTEMPTS_PER_CYCLE` 默认 30)次;周期
|
||||||
`LIVE_CYCLE_SECONDS` 默认 3600、最小 60 秒,通过 shutdown Event 等待。
|
`LIVE_CYCLE_SECONDS` 默认 3600、最小 60 秒,通过 shutdown Event 等待。
|
||||||
候选 A verified_reuse 优先,禁提过滤、GPU enum、占位符检查及远端、内置 ledger 和进程内去重。
|
候选 A verified_reuse 优先,禁提过滤、GPU enum、占位符检查及远端、内置 ledger 和进程内去重。
|
||||||
永久拒绝对保存在 `src/data/reject-marks.json`,随镜像重建保留;不写容器状态文件。
|
永久拒绝对保存在 `src/data/reject-marks.json`,随镜像重建保留;不写容器状态文件。
|
||||||
`LIVE_PRECHECK` 默认 1,只读预检已验证/验证中 GPU;`LIVE_MAX_PRECHECKS_PER_CYCLE` 默认 30。
|
`LIVE_PRECHECK` 默认 1,只读预检已验证/验证中 GPU;`LIVE_MAX_PRECHECKS_PER_CYCLE` 默认 60。
|
||||||
build-config 失败回退 sample 并记录原因;日志不记录响应正文或凭证。
|
build-config 失败回退 sample 并记录原因;日志不记录响应正文或凭证。
|
||||||
|
|
||||||
统计成功口径:成功指标统一为 `verify_pass`(`verifyResult==1`,验证通过);任务 status 为 `success` 但 `verifyResult==-1` 属于验证失败,积分为 0。 每轮日志格式为 `[live] stats total=N queue=N verify_pass=N verify_fail=N failed=N unknown=N points=X`;失败日志为 `[live] stats unavailable reason=<安全原因>`。
|
统计成功口径:成功指标统一为 `verify_pass`(`verifyResult==1`,验证通过);任务 status 为 `success` 但 `verifyResult==-1` 属于验证失败,积分为 0。 每轮日志格式为 `[live] stats total=N queue=N verify_pass=N verify_fail=N failed=N unknown=N points=X`;失败日志为 `[live] stats unavailable reason=<安全原因>`。
|
||||||
|
|||||||
@@ -71,18 +71,18 @@ def _run_cycle(candidates, client, queue_reader, now, stats_reader, precheck, fo
|
|||||||
line = 'unavailable reason=' + (reason if isinstance(reason, str) and reason in safe_reasons else 'read_failed')
|
line = 'unavailable reason=' + (reason if isinstance(reason, str) and reason in safe_reasons else 'read_failed')
|
||||||
print('[live] stats ' + line, file=sys.stderr)
|
print('[live] stats ' + line, file=sys.stderr)
|
||||||
try:
|
try:
|
||||||
cap = int(os.environ.get('LIVE_MAX_SUBMITS_PER_CYCLE', '3'))
|
cap = int(os.environ.get('LIVE_MAX_SUBMITS_PER_CYCLE', '6'))
|
||||||
if cap < 1:
|
if cap < 1:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
emit('hold', 'invalid_cycle_cap')
|
emit('hold', 'invalid_cycle_cap')
|
||||||
return decisions
|
return decisions
|
||||||
try:
|
try:
|
||||||
attempt_cap = int(os.environ.get('LIVE_MAX_ATTEMPTS_PER_CYCLE', '15'))
|
attempt_cap = int(os.environ.get('LIVE_MAX_ATTEMPTS_PER_CYCLE', '30'))
|
||||||
if attempt_cap < 1:
|
if attempt_cap < 1:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
attempt_cap = 15
|
attempt_cap = 30
|
||||||
if candidates is None:
|
if candidates is None:
|
||||||
try:
|
try:
|
||||||
if os.environ.get('PENDING_SUBMIT_MD') or os.environ.get('PENDING_SUBMIT_TXT'):
|
if os.environ.get('PENDING_SUBMIT_MD') or os.environ.get('PENDING_SUBMIT_TXT'):
|
||||||
@@ -122,11 +122,11 @@ def _run_cycle(candidates, client, queue_reader, now, stats_reader, precheck, fo
|
|||||||
emit('hold', 'state_read_failed')
|
emit('hold', 'state_read_failed')
|
||||||
return decisions
|
return decisions
|
||||||
try:
|
try:
|
||||||
precheck_cap = int(os.environ.get('LIVE_MAX_PRECHECKS_PER_CYCLE', '30'))
|
precheck_cap = int(os.environ.get('LIVE_MAX_PRECHECKS_PER_CYCLE', '60'))
|
||||||
if precheck_cap < 1:
|
if precheck_cap < 1:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
precheck_cap = 30
|
precheck_cap = 60
|
||||||
prechecks = 0
|
prechecks = 0
|
||||||
emitted_marks = set()
|
emitted_marks = set()
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class TestLiveCycle(unittest.TestCase):
|
|||||||
self.assertEqual(self.submitted_pairs(), [])
|
self.assertEqual(self.submitted_pairs(), [])
|
||||||
|
|
||||||
def test_attempt_cap_and_invalid_fallback(self):
|
def test_attempt_cap_and_invalid_fallback(self):
|
||||||
for value, expected in ((None, 15), ('bad', 15), ('0', 15), ('-1', 15), ('2', 2)):
|
for value, expected in ((None, 30), ('bad', 30), ('0', 30), ('-1', 30), ('2', 2)):
|
||||||
with self.subTest(value=value):
|
with self.subTest(value=value):
|
||||||
_marked.clear()
|
_marked.clear()
|
||||||
self.client.reset_mock()
|
self.client.reset_mock()
|
||||||
@@ -71,10 +71,10 @@ class TestLiveCycle(unittest.TestCase):
|
|||||||
self.assertEqual(decisions[-1]['reason'], 'attempt_cap')
|
self.assertEqual(decisions[-1]['reason'], 'attempt_cap')
|
||||||
|
|
||||||
def test_submit_cap_counts_successes_after_rejections(self):
|
def test_submit_cap_counts_successes_after_rejections(self):
|
||||||
self.client.submit.side_effect = [dict(http_status=200, platform_code=c) for c in (60020, 0, 60028, 0, 0)]
|
self.client.submit.side_effect = [dict(http_status=200, platform_code=c) for c in (60020, 0, 60028, 0, 0, 0, 0, 0)]
|
||||||
decisions = self.cycle([candidate(str(i)) for i in range(7)], (1,) * 5)
|
decisions = self.cycle([candidate(str(i)) for i in range(10)], (1,) * 8)
|
||||||
self.assertEqual(self.client.submit.call_count, 5)
|
self.assertEqual(self.client.submit.call_count, 8)
|
||||||
self.assertEqual(sum(d['action'] == 'submitted' for d in decisions), 3)
|
self.assertEqual(sum(d['action'] == 'submitted' for d in decisions), 6)
|
||||||
self.assertEqual(decisions[-1]['reason'], 'cycle_cap')
|
self.assertEqual(decisions[-1]['reason'], 'cycle_cap')
|
||||||
|
|
||||||
def test_task_limit_stops_cycle(self):
|
def test_task_limit_stops_cycle(self):
|
||||||
@@ -110,9 +110,10 @@ class TestLiveCycle(unittest.TestCase):
|
|||||||
self.assertIn('[live] hold', self.logs.getvalue())
|
self.assertIn('[live] hold', self.logs.getvalue())
|
||||||
|
|
||||||
def test_order_and_cap(self):
|
def test_order_and_cap(self):
|
||||||
rows = [candidate('b'), candidate('a', 'A'), candidate('verified', 'A', 'verified_reuse'), candidate('c'), candidate('d')]
|
rows = [candidate('b'), candidate('a', 'A'), candidate('verified', 'A', 'verified_reuse'), candidate('c'), candidate('d'), candidate('e'), candidate('f'), candidate('g')]
|
||||||
self.cycle(rows, (90, 90, 90))
|
self.cycle(rows, (90,) * 6)
|
||||||
self.assertEqual([c.args[0]['modelAddress'] for c in self.client.submit.call_args_list], ['org/verified', 'org/a', 'org/b'])
|
self.assertEqual([c.args[0]['modelAddress'] for c in self.client.submit.call_args_list],
|
||||||
|
['org/verified', 'org/a', 'org/b', 'org/c', 'org/d', 'org/e'])
|
||||||
self.assertIn('cycle_cap', self.logs.getvalue())
|
self.assertIn('cycle_cap', self.logs.getvalue())
|
||||||
|
|
||||||
def test_midcycle(self):
|
def test_midcycle(self):
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ class TestLiveMarks(unittest.TestCase):
|
|||||||
|
|
||||||
def test_precheck_cap(self):
|
def test_precheck_cap(self):
|
||||||
self.precheck.return_value = 'ok', {'Biren_166m': 60027}
|
self.precheck.return_value = 'ok', {'Biren_166m': 60027}
|
||||||
rows = [dict(model_address=f'unit/m{i}', targetGpu='壁砺') for i in range(32)]
|
rows = [dict(model_address=f'unit/m{i}', targetGpu='壁砺') for i in range(62)]
|
||||||
for value, count in (('2', 2), ('bad', 30), ('0', 30)):
|
for value, count in (('2', 2), ('bad', 60), ('0', 60)):
|
||||||
_marked.clear()
|
_marked.clear()
|
||||||
self.precheck.reset_mock()
|
self.precheck.reset_mock()
|
||||||
with patch.dict(os.environ, {'LIVE_MAX_PRECHECKS_PER_CYCLE': value, 'LIVE_MAX_ATTEMPTS_PER_CYCLE': '1'}):
|
with patch.dict(os.environ, {'LIVE_MAX_PRECHECKS_PER_CYCLE': value, 'LIVE_MAX_ATTEMPTS_PER_CYCLE': '1'}):
|
||||||
|
|||||||
Reference in New Issue
Block a user