Repository navigation
fix(server): resolve seven server defects found on 96dab686 - #325
Merged
Merged
Conversation
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
A non-streaming chat completion polls the sampler's future record until it reaches a terminal state. That loop had no total budget and never checked the client, so a stuck sampling task kept the gateway coroutine alive for the record's whole retention window (24h by default) while polling the shared state at 2 QPS. Bound the wait with TWINKLE_OPENAI_SAMPLE_TIMEOUT (default 1800s), stop as soon as the client disconnects, and map both to 504/timeout. A future record that is already gone is a server-side loss, so it now answers 500 instead of a 404 carrying a server_error type.
The previous summary preferred __cause__ for every exception, so a deliberate `raise RequestRejectedError(...) from exc` lost its user-facing message and could leak the inner detail into the wire error field. Take the wrapped cause only for RayTaskError (whose own str is the Ray call header), and fall back to the exception itself everywhere else.
The tinker route (dispatch='slice_dp' + GRPOLoss) and the Megatron backend reach the same cross-rank loss failure that stalls a DP replica, but only the Transformers twinkle-native path validated ahead of the forward. Move the preflight into the shared backend base class so both backends inherit one implementation, teach it the tinker payload (set up the loss from loss_fn, convert datums, extract RL features), call it from /tinker/forward_backward for GRPO batches, and warn instead of silently skipping when a custom backend has no preflight at all.
…l sites The full-mode test only exercised the claim helper, so reverting the handler to register_resource() still passed. Drive two real add_adapter_to_model calls through the route, interleaved inside register_model, and assert exactly one winner plus the loser's unload. Derive the dedup TTL expectation from the configured queue and execution windows instead of hard-coding 2160.
hjh0119
approved these changes
Oct 21, 2026
Yunnglin
added a commit
that referenced
this pull request
Oct 21, 2026
Integrate 015732e while preserving controller-owned training creation and sequence ledgers. Adapt full-mode regression tests to the catalog/controller path. Verified 434 targeted tests and the complete pre-commit suite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR type
PR information
Seven server defects reproduced on
main@96dab686are fixed here, one commit per defect. Each fix is minimal (root cause only), covered by a regression test, and re-verified against a live Ray Serve deployment.1.
checkpoint deletecould remove parent directoriesRoot cause
CheckpointManager.delete()only rejected..and then joinedcheckpoint_idas a relative path, soweights,sampler_weightsor.deleted real weight directories.Fix
delete()now resolves the checkpoint metadata first and returnsFalseunlesscheckpoint_idnames a real checkpoint. Aliases (latest), plain directories and traversal are rejected. Twinkle and Tinker routes share the implementation.2. Concurrent
add_adapter_to_modelbypassed full-mode exclusivityRoot cause The exclusivity check ran before queueing, while the resource registration happened inside the task after
await state.register_model(...), so two requests could both pass the check.Fix New
ModelManagement.claim_full_mode_resource()performs check + registration without anawaitin between; the full-mode creation task calls it right afterregister_model, making the transition atomic.3. OpenAI non-streaming responses had empty
choicesRoot cause
/twinkle/sampleanswers with aTaskEnvelope, but the non-streaming branch read the top-levelsampleskey and never polled an unfinished task.Fix The envelope is parsed and, when non-terminal, polled through the shared lifecycle state until it reaches a terminal status.
completedis translated as before;failed/cancelledmap to OpenAI-shaped errors; a completed task without samples returns 502 instead of an empty200.4. Dedup claim TTL did not cover queue wait
Root cause TTL was
effective_execution_timeout + 60, so a claim expired while the first copy was still queued.Fix TTL is now
queue_timeout + effective_execution_timeout + 60.5. One rank failing the loss check hung the other rank
Root cause Each rank ran forward → loss → backward independently; a rank that raised at the loss check left the peer entering DDP backward collective alone.
Fix Losses gained
validate_inputs();GRPOLossvalidates the alignment rules for the whole batch. The shared backend base class exposespreflight_loss_inputswithremote_function(dispatch='all', collect='none'), so every backend (Transformers and Megatron) inherits one implementation, and it also understands the tinker payload (set the loss up fromloss_fn, convert datums, extract their RL features). All three entry points call it before the sliced backend:/twinkle/forward_backward,/twinkle/forward_backward_from_data_plane, and/tinker/forward_backward(GRPO batches only; plain SFT pays no extra actor round trip). Every actor validates the complete batch before any rank enters forward, so failures are symmetric and fast. A backend without the hook now logs a warning instead of silently skipping the check.6. Client lost the underlying cause of a task failure
Root cause The server summarised a
RayTaskErrorby its first line (the Ray invocation header), andTaskFailedErrordroppedErrorPayload.traceback.Fix The worker now summarises the wrapped cause (
type: str(cause)), andTaskFailedErrorcarries the remotetraceback(populated by_future._unwrap).7. Automatic template setup always returned 422
Root cause
_ensure_templateput an undeclaredmodel_idat the top level of the sampler's strict request schema.Fix Only
template_clsandadapter_nameare sent; the sampler injects its ownmodel_idwhen the template constructor needs one. The per-model cache is still only populated on200.Review follow-ups
TWINKLE_OPENAI_SAMPLE_TIMEOUT(default 1800s), stops on disconnect, and answers 504/timeout; a lost future record answers 500 instead of 404-with-server_error.RayTaskErrorin the failure summary. Taking__cause__for every exception replaced deliberateraise X from emessages; the summary now unwraps Ray's transport wrapper only.protocol.types.TERMINAL_STATUSES(the gateway use-case layer keeps its own copy by design; a guard test forbids protocol imports there).add_adapter_to_modelcalls through the route (reverting the handler toregister_resourcefails it), the dedup TTL expectation is derived from the configured windows, the preflight test uses three inputs and asserts the full batch reaches preflight (plus a negative SFT case), and the backend entry point is asserted for all four concrete model classes.Experiment results
Environment: PPU,
twinkle_ppu_vllm. Earlier runs used A (LoRA + sampler), B (full mode) and C (LoRA DP=2); the tinker/Megatron checks used a 2-GPUQwen3.5-4BMegatron DP=2 deployment and the same A cluster with the Transformers backend.weights,sampler_weights,.) all return 404, run directory and weights intact.FullModeBusyError,/twinkle/statusreportsmodels == 1.choicesnon-empty ("2 + 3 equals 5.", usage 40/9/49); streaming unchanged.dedup_hit=True,enqueued_copies=1.forward_backwardcalls andclip_grad_and_stepsucceed; a malformed call fails in 0.25s;zero_gradand the nextforward_backwardsucceed; deep health stays 200 with no timeout./tinker/forward_backward, Megatron DP=2,Qwen3.5-4B): baseline step succeeds; a partially malformed GRPO batch fails in 0.75s with the alignment assertion; the next batch succeeds in 1.05s; deep health 200.str(e),e.traceback, and the HTTP envelope'serror.error/error.tracebackall containmismatch; recovery training and deep health succeed.body.model_id) and 200 for the body the gateway now sends; no gateway-originatedset_template 422in the server log and two consecutive OpenAI requests succeed.Tests:
pytest tests/server tests/loss tests/twinkle_client→ 787 passed, 71 skipped, 1 failed. The single failure (test_liger_fused_linear_ce.py::test_fused_kernel_success_does_not_mark_broken) reproduces on a clean96dab686worktree — the fused liger kernel is unsupported on this device and falls back — so it is unrelated to this PR.pre-commit run --files <changed>passes.Scope notes
TwinkleCompatMegatronModel,TwinkleCompatFullMegatronModel,TwinkleCompatTransformersModelandTwinkleCompatFullTransformersModel._ensure_templateis a safety net; the fix guarantees the body it sends is schema-valid.