[Bug] Colocate memory swap: SGLang control-plane 502 与 release-after-schedule 竞态均触发 global restart
本 issue 汇总同一次 baseline 实验中出现的两类 colocate 显存切换相关故障,均会触发 full global restart(训练从 step 0 重跑、消耗 max_global_restart 预算):
- Bug A(control-plane 502):每步 weight sync 的 SGLang control-plane HTTP 调用遇瞬时
502 Bad Gateway,无 retry 保护直接冒泡。
- Bug B(release-after-schedule 竞态):
release_memory_occupation 释放 KV/token-pool 显存后,scheduler 仍调度残留 prefill,Triton kernel 访问已释放指针崩溃(SIGQUIT)。
两者同源于 colocate 的 memory offload/onload 与 SGLang 调度之间缺乏严格屏障,但 root cause 与修法不同,分列如下。
- 模型 / 配置:Qwen3-VL-4B-Instruct,colocate,n=8,150 step,seed 1234
- Relax base commit:
9ce329c8bff57a240aad3e230f21aab051adbc38
- SGLang engine:8 个,
rollout-num-gpus-per-engine=1,sglang-mem-fraction-static=0.8
- 严重级别:High(长训练几乎必然被打断,restart 预算耗尽后直接失败退出)
Bug A — control-plane HTTP calls trigger global restart on transient SGLang 502
Summary
在 Qwen3-VL-4B 8×GPU colocate GRPO 训练中,每步 weight sync 与 memory offload/onload 依赖的 SGLang control-plane HTTP 调用,会在 SGLang scheduler 换显存的窗口内瞬时返回 502 Bad Gateway。这些调用没有 retry 保护,单次 502 会冒泡为 actor unhealthy,触发 full global restart,训练从 step 0 重跑。
同一次 baseline 实验中先后被打挂 2 次,每次消耗一次 global restart 预算(max_global_restart=3)并作废全部已训练 step,各造成约 8–9 分钟恢复停机。
What happened
| # |
时间 |
崩溃 step |
触发 endpoint |
调用来源 |
| Crash 1 |
2026-08-17 13:42:54 |
step 62 |
POST /resume_memory_occupation → 502 |
rollout→train memory onload |
| Crash 2 |
2026-08-17 23:20:39 |
step 39 |
POST /pause_generation → 502 |
每步 weight sync 前 pause |
两次都是:502 Bad Gateway(response.text='')→ Service 'actor' detected as unhealthy → Triggering global restart due to: actor failure → Starting GLOBAL restart → 约 8 分钟后 Global restart completed, re-running training loop,训练从 step 0 重新开始。
全实验期间真实 502 仅 8 次(Crash 1 run 3 次全在 /resume_memory_occupation;Crash 2 run 4 次全在 /pause_generation),其中各有 1 次冒泡触发 restart。
Key logs
Crash 2(step 39,/pause_generation)完整 traceback:
File ".../relax/backends/megatron/actor.py", line 1657, in update_weights
self.weight_updater.update_weights()
File ".../relax/backends/megatron/weight_update/update_weight_from_tensor.py", line 203, in update_weights
ray.get([engine.pause_generation.remote() for engine in all_engines])
ray.exceptions.RayTaskError(HTTPError): ray::SGLangEngine.pause_generation()
File ".../relax/backends/sglang/sglang_engine.py", line 1051, in pause_generation
response.raise_for_status()
requests.exceptions.HTTPError: 502 Server Error: Bad Gateway for url:
http://<engine>:<port>/pause_generation, triggering restart
2026-08-17 23:20:39 | WARNING | relax.core.controller:269 Service 'actor' detected as unhealthy, initiating restart...
2026-08-17 23:20:42 | INFO | relax.core.controller:803 === Starting GLOBAL restart (max=3, full Controller re-initialization from zero) ===
2026-08-17 23:28:40 | INFO | relax.core.controller:675 Global restart completed, re-running training loop
Crash 1(step 62,/resume_memory_occupation):
File ".../relax/distributed/ray/rollout.py", line 1164, in onload_kv
await self.onload(tags=[GPU_MEMORY_TYPE_KV_CACHE, GPU_MEMORY_TYPE_CUDA_GRAPH])
File ".../relax/backends/sglang/sglang_engine.py", line 959, in resume_memory_occupation
return self._make_request("resume_memory_occupation", {"tags": tags})
requests.exceptions.HTTPError: 502 Server Error: Bad Gateway for url:
http://<engine>:<port>/resume_memory_occupation
response.text='', triggering restart
SGLang engine state
- 502 前后 engine 未崩溃:日志中所有
dead/killed 均为 The actor is dead because it was killed by ray.kill.,即 restart 流程主动 kill;无 scheduler crash、无 CUDA OOM、无 SIGSEGV/SIGABRT、无 core dump。
- engine 侧仅出现 502 与
Request is disconnected from the client side (type 1). Abort request——后者是 client 因 502 断开后 engine 端 abort in-flight request,均为瞬时忙碌而非故障。
- 旁证:sgl-router 对各 engine 的
/health probe 全程偶发 TimedOut(约每小时 10 条,共 99 条),但从未连续达到 router_health_failure_threshold=3,router 从未真正 eject 任何 engine。与 502 同源——colocate memory swap 窗口内 engine HTTP front-end 短暂不可应答。
Root cause
Colocate 模式每个 step 执行:pause_generation → flush_cache → release_memory_occupation → 训练 → resume_memory_occupation → continue_generation。
这些 control-plane HTTP 调用恰好落在 SGLang scheduler 忙于 GPU memory offload/reload 的窗口内,此时 HTTP front-end 短暂无法把请求投递给后端 scheduler,返回 502 Bad Gateway(空 body)。这是瞬时、可重试的,engine 本身健康。
问题在于错误处理不对称:
update_weights_from_tensor 走 _make_request(..., max_attempts=3, retry_status_codes={502,503,504}),有 retry;
- 同属每步 hot path 的
pause_generation / continue_generation 是裸 requests.post(...).raise_for_status(),resume_memory_occupation / release_memory_occupation 走 _make_request 但用默认 max_attempts=1——都没有 retry。
于是任何一个瞬时 502 直接抛出 → controller 判定 actor unhealthy → global restart。本应被一次 backoff retry 吸收的抖动,被放大成整轮训练重跑。
Impact
- 每次误 restart:训练从 step 0 重跑(已训 step 全部作废)+ 约 8–9 分钟恢复停机 + 消耗 1 次
max_global_restart 预算(默认 3)。
- 长训练(150+ step、数十小时)几乎必然多次触发;预算耗尽后训练直接失败退出。
- colocate 模式下模型越大 / memory swap 越重,撞上忙窗口概率越高。
Proposed fix
将每步 hot path 上所有 SGLang control-plane HTTP 调用统一走带 retry 的 _make_request(对 502/503/504 + connection/timeout 做 exponential backoff retry,如 3 次、0.5s 起):
pause_generation / continue_generation:由裸 requests.post 改为 _make_request(..., max_attempts=3, retry_status_codes={502,503,504});
resume_memory_occupation / release_memory_occupation:补上 max_attempts=3, retry_status_codes={502,503,504};
update_weights_from_tensor:已具备,无需改动;
flush_cache:已有自带 deadline loop、容忍 non-200,无需改动。
非每步 hot path、或已被 caller try/timeout 包裹、失败不会触发 restart 的调用(abort_requests、get_weight_version、check_weights、router register/unregister、health_generate、get_server_info)保持不变。
备注:本地实验环境已按上述方案修补并验证——resume_memory_occupation / release_memory_occupation 修补后成功吞掉 2 次瞬时 502;随后补齐 pause_generation / continue_generation。此 issue 用于上游沉淀 root cause 与通用修复。
Repro (Bug A)
- Qwen3-VL-4B 8×GPU colocate GRPO,
n=8,long prompt(≤12288),long run(≥150 step)。
- 观察每步 weight sync / memory offload-onload;在 SGLang scheduler memory swap 繁忙窗口内,control-plane 调用偶发
502 Bad Gateway。
- 单次 502 即触发 global restart,训练从 step 0 重跑。
Bug B — release_memory_occupation 后残留 prefill 撞已释放 KV pool,Triton 崩溃
Summary
在 Bug A 的 retry 补丁全部生效(全程 0 次 502-触发 restart)之后,同一实验在 step 56 又被另一类故障打挂:单个 SGLang engine 的 scheduler 进程抛 ValueError: Pointer argument (at 0) cannot be accessed from Triton (cpu tensor?) 后 SIGQUIT 退出,触发 global restart。这是数据面竞态,与 Bug A 的 control-plane HTTP 无关,retry 补丁对它无效。
What happened
| 时间 |
崩溃 step |
崩溃 engine |
现象 |
| 2026-08-18 11:58:09 |
step 56 |
8 个中的 1 个 |
scheduler event_loop_overlap 内 Triton kernel 拿到非法指针 → SIGQUIT → global restart |
只有 1/8 engine 崩溃(其余 7 个无此异常),是典型竞态特征。
Key logs
崩溃 engine 自身的时间线(关键在 release 与 crash 的先后):
[2026-08-18 11:57:44] GET /flush_cache 200 → Cache flushed successfully!
[2026-08-18 11:57:45] POST /release_memory_occupation 200 OK ← KV / req_to_token pool 显存被释放
[2026-08-18 11:58:09] Scheduler hit an exception: Traceback (most recent call last):
scheduler.event_loop_overlap → get_next_batch_to_run → get_new_batch_prefill
→ prepare_for_extend → alloc_for_extend → write_cache_indices
→ write_req_to_token_pool_triton[(req_pool_indices_tensor.shape[0],)](
req_to_token_pool.req_to_token, ... )
ValueError: Pointer argument (at 0) cannot be accessed from Triton (cpu tensor?)
[2026-08-18 11:58:09] SIGQUIT received. signum=None ... It usually means one child failed.
Root cause
崩溃路径(SGLang srt/mem_cache/common.py::write_cache_indices → write_req_to_token_pool_triton)会把 req_to_token_pool.req_to_token 的 data_ptr() 传给 Triton kernel。时间线显示 crash 前 24 秒该 engine 刚成功执行 flush_cache + release_memory_occupation——KV / token-pool 显存已被 torch memory saver 释放/换出,此时该 tensor 的指针不再指向合法 GPU 显存(退化为 cpu/空指针)。但 scheduler 的 event_loop_overlap 仍从队列里取到一个残留的 prefill 请求去 prepare_for_extend → write_cache_indices,对着已释放的 pool 发起 Triton 写入,于是报 Pointer argument cannot be accessed from Triton (cpu tensor?)。
即:release_memory_occupation(colocate 交显存给训练)与 scheduler event loop 之间存在竞态——release 之前虽 flush_cache,但没有停住 scheduler 调度,释放显存后仍有 in-flight / 新入队 prefill 被调度,访问已释放的 KV pool 指针而崩溃。
放大因素:崩溃发生在 response length collapse 的极短序列稳态期,每步 rollout 极快结束、请求在 offload 边界密集涌动,显著提高了"release 后仍有 prefill 待调度"的命中概率。
Impact
- 单 engine scheduler crash →
SIGQUIT → actor failure → global restart,训练从 step 0 重跑并消耗 1 次 max_global_restart 预算。
- 与 Bug A 叠加会更快耗尽 restart 预算;Bug A 修好后此 Bug B 仍能独立打断长训练。
Proposed fix (Bug B)
代码核对确认:普通 rollout engine 用的是 base SGLangEngine.release_memory_occupation(relax/backends/sglang/sglang_engine.py:967),其 body 只有 flush_cache() + release_memory_occupation HTTP,没有 pause_generation / abort_requests;配套的 base resume_memory_occupation(同文件 :975)也没有 continue_generation。调用链 RolloutServer._offload_local(relax/distributed/ray/rollout.py:1136)→ health_monitoring_pause()(只暂停 relax 侧健康监控,不碰 SGLang scheduler)→ srv.offload() → EngineGroup.offload() → engine.release_memory_occupation.remote(),整条路径没有任何东西停住 SGLang 的 event loop。因此 release 释放显存后,scheduler 仍可调度残留 / 新入队 prefill,撞上已释放的 KV pool 指针崩溃。
对照组已证明修法正确:GenRMEngine.release_memory_occupation(override,同文件 :1157)先 _pause_generation_for_offload(pause_generation abort 模式,:1228)停调度、再循环 abort_requests + flush_cache drain,最后才 release;其 resume_memory_occupation(override,:1216)在 onload 后 continue_generation 重开 admission。该 override 的注释已明确记载同一崩因:“a straggler agentic /generate admitted between our flush and the release crashes the scheduler”。
精确改动点: 把 GenRM 的屏障下沉到 base 类,让普通 rollout engine 也走「pause(abort) → abort_requests → flush → release … resume → continue」:
SGLangEngine.release_memory_occupation(:967):在 flush_cache() 之前先 pause_generation(abort 模式)停止 admission 并 abort in-flight;可复用 GenRM 的 drain 循环思路(带 wall-clock deadline,避免 wedged scheduler 挂死 rank-0)。
SGLangEngine.resume_memory_occupation(:975):在 resume 成功(full onload,tags is None 或含 KV)后补 continue_generation,与上面的 pause 成对,否则 admission 永久关闭、生成停摆。
- 成对性必须保证:只加 pause 不加 continue 会 hang;GenRM 已是成对实现,可作为参考或直接把公共逻辑提取到 base 由两者共用。
这样即可消除 release 后残留 prefill 撞空指针的竞态,且与 Bug A 的 HTTP retry 修补正交、互不影响。
(附:SGLang 的 release_memory_occupation 内部会 assert scheduler 空闲(_is_no_request);本竞态发生在 release 成功返回“之后”的下一拍 prefill,因此仅靠 flush 不足以覆盖,必须 pause 住 admission 窗口直到 resume。)
Repro (Bug B)
- Qwen3-VL-4B 8×GPU colocate GRPO,
n=8,long run;attention backend 走 Triton 分支的 write_req_to_token_pool。
- 进入 response 变短、rollout 高频、每步 offload/onload 密集的阶段。
- 某个 engine 在
release_memory_occupation 后仍被调度到 prefill,Triton 访问已释放 KV pool 指针崩溃、SIGQUIT,触发 global restart。
Environment
- Relax base commit
9ce329c8bff57a240aad3e230f21aab051adbc38(branch feat/sglang-native-group-sampling)
- 模型 Qwen3-VL-4B-Instruct;colocate;8×H800
- SGLang 8 engine,
rollout-num-gpus-per-engine=1,sglang-mem-fraction-static=0.8
- 相关 health-check 配置:
rollout_health_check_interval=30、rollout_health_check_timeout=30、router_health_check_timeout_secs=5、router_health_failure_threshold=3、max_global_restart=3
Load-affecting parameters(决定 offload/onload 边界的请求密度与 KV 占用,直接影响两个 bug 的触发概率)
每步 rollout / 显存交接的负载由以下生效参数决定,复现与判定严重度时需一并对齐:
| 参数 |
值 |
说明 |
--rollout-batch-size |
64 |
每 step prompt 数 |
--n-samples-per-prompt |
8 |
每 prompt 采样数 |
| responses/step |
512 |
= 64 × 8,每 step 主 /generate 请求量(8 engine 分摊 → 单 engine 峰值 in-flight 高,offload 边界残留请求概率大) |
--global-batch-size |
512 |
训练 global batch |
--rollout-max-prompt-len |
12288 |
prompt 上限(长 prompt → KV 占用大) |
--rollout-max-response-len |
512 |
response 上限 |
--rollout-temperature |
1.0 |
|
--num-rollout |
150 |
训练步数 |
--max-tokens-per-gpu |
25600 |
训练侧 per-GPU token 上限(dynamic batch) |
--log-probs-max-tokens-per-gpu |
49152 |
logprob 阶段 per-GPU token 上限 |
--use-dynamic-batch-size |
True |
动态 batch |
--tensor-model-parallel-size |
2 |
训练 TP(actor 侧) |
--colocate |
True |
actor 与 rollout 时分复用同一批 GPU(每步 offload/onload 的根源) |
SGLang engine 侧(每个 engine,rollout-num-gpus-per-engine=1,mem-fraction-static=0.8)实测:
max_total_num_tokens=379622 chunked_prefill_size=8192 max_prefill_tokens=16384
max_running_requests=2048 context_len=262144 available_gpu_mem≈12.2 GB
关键关联:
- 单 step 512 个请求 / 8 engine ≈ 64 req/engine 峰值,配合
max_running_requests=2048 的高并发,offload 触发时 in-flight/排队请求多,放大 Bug B 的"release 后残留 prefill"竞态。
mem-fraction-static=0.8 + 长 prompt(12288) 使 KV pool 占满 engine 显存的大部分,offload 时释放/换出的显存块大,resume_memory_occupation 的换回耗时长,放大 Bug A 的 502 忙窗口。
[Bug] Colocate memory swap: SGLang control-plane 502 与 release-after-schedule 竞态均触发 global restart
本 issue 汇总同一次 baseline 实验中出现的两类 colocate 显存切换相关故障,均会触发 full global restart(训练从 step 0 重跑、消耗
max_global_restart预算):502 Bad Gateway,无 retry 保护直接冒泡。release_memory_occupation释放 KV/token-pool 显存后,scheduler 仍调度残留 prefill,Triton kernel 访问已释放指针崩溃(SIGQUIT)。两者同源于 colocate 的 memory offload/onload 与 SGLang 调度之间缺乏严格屏障,但 root cause 与修法不同,分列如下。
9ce329c8bff57a240aad3e230f21aab051adbc38rollout-num-gpus-per-engine=1,sglang-mem-fraction-static=0.8Bug A — control-plane HTTP calls trigger global restart on transient SGLang 502
Summary
在 Qwen3-VL-4B 8×GPU colocate GRPO 训练中,每步 weight sync 与 memory offload/onload 依赖的 SGLang control-plane HTTP 调用,会在 SGLang scheduler 换显存的窗口内瞬时返回
502 Bad Gateway。这些调用没有 retry 保护,单次 502 会冒泡为 actor unhealthy,触发 full global restart,训练从 step 0 重跑。同一次 baseline 实验中先后被打挂 2 次,每次消耗一次 global restart 预算(
max_global_restart=3)并作废全部已训练 step,各造成约 8–9 分钟恢复停机。What happened
POST /resume_memory_occupation→ 502POST /pause_generation→ 502两次都是:
502 Bad Gateway(response.text='')→Service 'actor' detected as unhealthy→Triggering global restart due to: actor failure→Starting GLOBAL restart→ 约 8 分钟后Global restart completed, re-running training loop,训练从 step 0 重新开始。全实验期间真实 502 仅 8 次(Crash 1 run 3 次全在
/resume_memory_occupation;Crash 2 run 4 次全在/pause_generation),其中各有 1 次冒泡触发 restart。Key logs
Crash 2(step 39,
/pause_generation)完整 traceback:Crash 1(step 62,
/resume_memory_occupation):SGLang engine state
dead/killed均为The actor is dead because it was killed by ray.kill.,即 restart 流程主动 kill;无 scheduler crash、无 CUDA OOM、无 SIGSEGV/SIGABRT、无 core dump。Request is disconnected from the client side (type 1). Abort request——后者是 client 因 502 断开后 engine 端 abort in-flight request,均为瞬时忙碌而非故障。/healthprobe 全程偶发TimedOut(约每小时 10 条,共 99 条),但从未连续达到router_health_failure_threshold=3,router 从未真正 eject 任何 engine。与 502 同源——colocate memory swap 窗口内 engine HTTP front-end 短暂不可应答。Root cause
Colocate 模式每个 step 执行:
pause_generation→flush_cache→release_memory_occupation→ 训练 →resume_memory_occupation→continue_generation。这些 control-plane HTTP 调用恰好落在 SGLang scheduler 忙于 GPU memory offload/reload 的窗口内,此时 HTTP front-end 短暂无法把请求投递给后端 scheduler,返回
502 Bad Gateway(空 body)。这是瞬时、可重试的,engine 本身健康。问题在于错误处理不对称:
update_weights_from_tensor走_make_request(..., max_attempts=3, retry_status_codes={502,503,504}),有 retry;pause_generation/continue_generation是裸requests.post(...).raise_for_status(),resume_memory_occupation/release_memory_occupation走_make_request但用默认max_attempts=1——都没有 retry。于是任何一个瞬时 502 直接抛出 → controller 判定 actor unhealthy → global restart。本应被一次 backoff retry 吸收的抖动,被放大成整轮训练重跑。
Impact
max_global_restart预算(默认 3)。Proposed fix
将每步 hot path 上所有 SGLang control-plane HTTP 调用统一走带 retry 的
_make_request(对502/503/504+ connection/timeout 做 exponential backoff retry,如 3 次、0.5s 起):pause_generation/continue_generation:由裸requests.post改为_make_request(..., max_attempts=3, retry_status_codes={502,503,504});resume_memory_occupation/release_memory_occupation:补上max_attempts=3, retry_status_codes={502,503,504};update_weights_from_tensor:已具备,无需改动;flush_cache:已有自带 deadline loop、容忍 non-200,无需改动。非每步 hot path、或已被 caller
try/timeout包裹、失败不会触发 restart 的调用(abort_requests、get_weight_version、check_weights、router register/unregister、health_generate、get_server_info)保持不变。Repro (Bug A)
n=8,long prompt(≤12288),long run(≥150 step)。502 Bad Gateway。Bug B —
release_memory_occupation后残留 prefill 撞已释放 KV pool,Triton 崩溃Summary
在 Bug A 的 retry 补丁全部生效(全程 0 次 502-触发 restart)之后,同一实验在 step 56 又被另一类故障打挂:单个 SGLang engine 的 scheduler 进程抛
ValueError: Pointer argument (at 0) cannot be accessed from Triton (cpu tensor?)后SIGQUIT退出,触发 global restart。这是数据面竞态,与 Bug A 的 control-plane HTTP 无关,retry 补丁对它无效。What happened
event_loop_overlap内 Triton kernel 拿到非法指针 →SIGQUIT→ global restart只有 1/8 engine 崩溃(其余 7 个无此异常),是典型竞态特征。
Key logs
崩溃 engine 自身的时间线(关键在 release 与 crash 的先后):
Root cause
崩溃路径(SGLang
srt/mem_cache/common.py::write_cache_indices→write_req_to_token_pool_triton)会把req_to_token_pool.req_to_token的data_ptr()传给 Triton kernel。时间线显示 crash 前 24 秒该 engine 刚成功执行flush_cache+release_memory_occupation——KV / token-pool 显存已被 torch memory saver 释放/换出,此时该 tensor 的指针不再指向合法 GPU 显存(退化为 cpu/空指针)。但 scheduler 的event_loop_overlap仍从队列里取到一个残留的 prefill 请求去prepare_for_extend → write_cache_indices,对着已释放的 pool 发起 Triton 写入,于是报Pointer argument cannot be accessed from Triton (cpu tensor?)。即:
release_memory_occupation(colocate 交显存给训练)与 scheduler event loop 之间存在竞态——release 之前虽flush_cache,但没有停住 scheduler 调度,释放显存后仍有 in-flight / 新入队 prefill 被调度,访问已释放的 KV pool 指针而崩溃。放大因素:崩溃发生在 response length collapse 的极短序列稳态期,每步 rollout 极快结束、请求在 offload 边界密集涌动,显著提高了"release 后仍有 prefill 待调度"的命中概率。
Impact
SIGQUIT→ actor failure → global restart,训练从 step 0 重跑并消耗 1 次max_global_restart预算。Proposed fix (Bug B)
代码核对确认:普通 rollout engine 用的是 base
SGLangEngine.release_memory_occupation(relax/backends/sglang/sglang_engine.py:967),其 body 只有flush_cache()+release_memory_occupationHTTP,没有pause_generation/abort_requests;配套的 baseresume_memory_occupation(同文件:975)也没有continue_generation。调用链RolloutServer._offload_local(relax/distributed/ray/rollout.py:1136)→health_monitoring_pause()(只暂停 relax 侧健康监控,不碰 SGLang scheduler)→srv.offload()→EngineGroup.offload()→engine.release_memory_occupation.remote(),整条路径没有任何东西停住 SGLang 的 event loop。因此 release 释放显存后,scheduler 仍可调度残留 / 新入队 prefill,撞上已释放的 KV pool 指针崩溃。对照组已证明修法正确:
GenRMEngine.release_memory_occupation(override,同文件:1157)先_pause_generation_for_offload(pause_generationabort 模式,:1228)停调度、再循环abort_requests+flush_cachedrain,最后才 release;其resume_memory_occupation(override,:1216)在 onload 后continue_generation重开 admission。该 override 的注释已明确记载同一崩因:“a straggler agentic /generate admitted between our flush and the release crashes the scheduler”。精确改动点: 把 GenRM 的屏障下沉到 base 类,让普通 rollout engine 也走「pause(abort) → abort_requests → flush → release … resume → continue」:
SGLangEngine.release_memory_occupation(:967):在flush_cache()之前先pause_generation(abort 模式)停止 admission 并 abort in-flight;可复用 GenRM 的 drain 循环思路(带 wall-clock deadline,避免 wedged scheduler 挂死 rank-0)。SGLangEngine.resume_memory_occupation(:975):在 resume 成功(full onload,tags is None或含 KV)后补continue_generation,与上面的 pause 成对,否则 admission 永久关闭、生成停摆。这样即可消除 release 后残留 prefill 撞空指针的竞态,且与 Bug A 的 HTTP retry 修补正交、互不影响。
(附:SGLang 的
release_memory_occupation内部会 assert scheduler 空闲(_is_no_request);本竞态发生在 release 成功返回“之后”的下一拍 prefill,因此仅靠 flush 不足以覆盖,必须 pause 住 admission 窗口直到 resume。)Repro (Bug B)
n=8,long run;attention backend 走 Triton 分支的write_req_to_token_pool。release_memory_occupation后仍被调度到 prefill,Triton 访问已释放 KV pool 指针崩溃、SIGQUIT,触发 global restart。Environment
9ce329c8bff57a240aad3e230f21aab051adbc38(branchfeat/sglang-native-group-sampling)rollout-num-gpus-per-engine=1,sglang-mem-fraction-static=0.8rollout_health_check_interval=30、rollout_health_check_timeout=30、router_health_check_timeout_secs=5、router_health_failure_threshold=3、max_global_restart=3Load-affecting parameters(决定 offload/onload 边界的请求密度与 KV 占用,直接影响两个 bug 的触发概率)
每步 rollout / 显存交接的负载由以下生效参数决定,复现与判定严重度时需一并对齐:
--rollout-batch-size--n-samples-per-prompt/generate请求量(8 engine 分摊 → 单 engine 峰值 in-flight 高,offload 边界残留请求概率大)--global-batch-size--rollout-max-prompt-len--rollout-max-response-len--rollout-temperature--num-rollout--max-tokens-per-gpu--log-probs-max-tokens-per-gpu--use-dynamic-batch-size--tensor-model-parallel-size--colocateSGLang engine 侧(每个 engine,
rollout-num-gpus-per-engine=1,mem-fraction-static=0.8)实测:关键关联:
max_running_requests=2048的高并发,offload 触发时 in-flight/排队请求多,放大 Bug B 的"release 后残留 prefill"竞态。mem-fraction-static=0.8+ 长 prompt(12288) 使 KV pool 占满 engine 显存的大部分,offload 时释放/换出的显存块大,resume_memory_occupation的换回耗时长,放大 Bug A 的 502 忙窗口。