chore: vendor sglang v0.5.10 snapshot

This commit is contained in:
2026-04-24 12:29:36 +00:00
parent 78f0d15221
commit bded08301f
4308 changed files with 1200894 additions and 2 deletions

View File

@@ -0,0 +1,63 @@
name: Check Maintenance Mode
description: Blocks CI when maintenance mode is active (issue #21065 is open), unless the PR has the bypass-maintenance label, or env SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN=true (PR Test workflow on main only). Merging non-CI-fix PRs is prohibited during maintenance mode; in severe cases, merge permissions may be revoked.
inputs:
github-token:
description: GitHub token for API access
required: false
default: ${{ github.token }}
runs:
using: composite
steps:
- name: Check maintenance mode
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
run: |
MAINTENANCE_ISSUE=21065
REPO="${{ github.repository }}"
PR_NUMBER="${{ github.event.pull_request.number }}"
# PR Test workflow only: scheduled runs and runs on main (dispatch / workflow_call) set this env
if [[ "${SGLANG_PR_TEST_BYPASS_MAINTENANCE_ON_MAIN:-}" == "true" ]]; then
echo "✅ PR Test on main branch; bypassing maintenance gate."
exit 0
fi
# Check if maintenance issue is open (fail-open: if API errors, allow CI to proceed)
ISSUE_STATE=$(gh issue view "$MAINTENANCE_ISSUE" --repo "$REPO" --json state --jq '.state' 2>/dev/null || echo "UNKNOWN")
if [[ "$ISSUE_STATE" != "OPEN" ]]; then
echo "✅ Maintenance mode is OFF. Proceeding with CI."
exit 0
fi
# For PRs, check if bypass-maintenance label is present
if [[ -n "$PR_NUMBER" ]]; then
HAS_BYPASS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '[.labels[].name] | map(select(. == "bypass-maintenance")) | length' 2>/dev/null || echo "0")
if [[ "$HAS_BYPASS" -gt 0 ]]; then
echo "✅ PR #$PR_NUMBER has 'bypass-maintenance' label. Bypassing maintenance mode."
exit 0
fi
fi
MSG=$(printf "%s\n" \
"## ⚠️ CI Maintenance Mode is Active" \
"The CI infrastructure is currently under maintenance." \
"All PR CI runs are paused until maintenance is complete." \
"**Merging non-CI-fix PRs is prohibited during maintenance mode.** In severe cases, merge permissions may be revoked." \
"You might also experience unexpected failures during this period." \
"The team is working on the issue and will update the status as soon as possible." \
"" \
"What should you do?" \
"- **Do NOT merge non-CI-fix PRs** until maintenance mode is lifted" \
"- Check back later (~12 hours)" \
"- Follow CI Maintenance Mode issue: https://github.com/$REPO/issues/$MAINTENANCE_ISSUE for status updates")
echo "$MSG" >> "$GITHUB_STEP_SUMMARY"
while IFS= read -r line; do
echo "::error::$line"
done <<< "$MSG"
exit 1

View File

@@ -0,0 +1,50 @@
name: Check Stage Health
description: Fail fast if any job in the current workflow run has already failed. Auto-skips for scheduled runs.
inputs:
github-token:
description: 'GitHub token for API calls'
required: false
default: ${{ github.token }}
runs:
using: composite
steps:
- name: Check stage health
uses: actions/github-script@v7
env:
SKIP_STAGE_HEALTH_CHECK: ${{ env.SKIP_STAGE_HEALTH_CHECK }}
with:
github-token: ${{ inputs.github-token }}
script: |
// Skip when explicitly requested via env var (e.g. release branch cut)
if (process.env.SKIP_STAGE_HEALTH_CHECK === 'true') {
core.info('Skipping health check (SKIP_STAGE_HEALTH_CHECK=true)');
return;
}
// Skip for scheduled runs — they should collect all failures, not fast-fail
if (context.eventName === 'schedule') {
core.info('Skipping health check for scheduled run');
return;
}
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
per_page: 100,
});
// Find jobs that failed from a real error, not from fast-fail cascade
const rootCauseFailures = jobs.filter(j => {
if (j.status !== 'completed' || j.conclusion !== 'failure') return false;
// If the failing step is the health check, it's a cascade — skip it
const failedStep = (j.steps || []).find(s => s.conclusion === 'failure');
if (failedStep && (failedStep.name.includes('check-stage-health') || failedStep.name.includes('Check stage health'))) {
return false;
}
return true;
});
if (rootCauseFailures.length > 0) {
core.setFailed(`Fast-fail: skipping — root cause job(s): ${rootCauseFailures.map(j => j.name).join(', ')}`);
}

View File

@@ -0,0 +1,27 @@
name: Upload CUDA Coredumps
description: Upload CUDA coredump files as artifacts and clean up the directory.
inputs:
artifact-suffix:
description: Suffix appended to the artifact name (e.g. matrix partition id)
required: false
default: ""
retention-days:
description: Number of days to retain the artifact
required: false
default: "7"
runs:
using: composite
steps:
- name: Upload CUDA coredumps
uses: actions/upload-artifact@v4
with:
name: cuda-coredumps-${{ github.job }}${{ inputs.artifact-suffix && format('-{0}', inputs.artifact-suffix) }}
path: ${{ env.SGLANG_CUDA_COREDUMP_DIR || '/tmp/sglang_cuda_coredumps' }}/
retention-days: ${{ inputs.retention-days }}
if-no-files-found: ignore
- name: Cleanup CUDA coredumps
shell: bash
run: rm -rf "${{ env.SGLANG_CUDA_COREDUMP_DIR || '/tmp/sglang_cuda_coredumps' }}"

View File

@@ -0,0 +1,177 @@
name: Wait for Jobs
description: Poll and wait for specified jobs in the current workflow run to complete
inputs:
stage-name:
description: 'Human-readable stage name for log messages (e.g. "stage-a")'
required: true
jobs:
description: |
JSON array of job specs to wait for. Each element is either:
- a string: exact job name (e.g. "stage-a-test-1-gpu-small")
- an object { "prefix": "...", "expected_count": N }: for matrix jobs
required: true
max-wait-minutes:
description: 'Maximum time to wait before timing out'
required: false
default: '240'
poll-interval-seconds:
description: 'Seconds between polling attempts'
required: false
default: '60'
github-token:
description: 'GitHub token for API calls'
required: false
default: ${{ github.token }}
outputs:
result:
description: 'Overall result: success, failure, or timeout'
value: ${{ steps.wait.outputs.result }}
runs:
using: composite
steps:
- name: Wait for jobs to complete
id: wait
uses: actions/github-script@v7
env:
INPUT_STAGE_NAME: ${{ inputs.stage-name }}
INPUT_JOBS: ${{ inputs.jobs }}
INPUT_MAX_WAIT_MINUTES: ${{ inputs.max-wait-minutes }}
INPUT_POLL_INTERVAL_SECONDS: ${{ inputs.poll-interval-seconds }}
with:
github-token: ${{ inputs.github-token }}
script: |
const stageName = process.env.INPUT_STAGE_NAME;
const jobSpecs = JSON.parse(process.env.INPUT_JOBS);
const maxWaitMinutes = parseInt(process.env.INPUT_MAX_WAIT_MINUTES);
const pollIntervalSeconds = parseInt(process.env.INPUT_POLL_INTERVAL_SECONDS);
const maxAttempts = (maxWaitMinutes * 60) / pollIntervalSeconds;
// Normalize job specs into a uniform format
const normalizedSpecs = jobSpecs.map(spec => {
if (typeof spec === 'string') {
return { prefix: spec, expected_count: 1, exact: true };
}
return { ...spec, exact: false };
});
const totalExpectedJobs = normalizedSpecs.reduce((sum, s) => sum + s.expected_count, 0);
const matchesSpec = (jobName, spec) => {
if (spec.exact) {
return jobName === spec.prefix;
}
return jobName === spec.prefix || jobName.startsWith(spec.prefix + ' (');
};
// Use ETag conditional requests to avoid consuming rate limit when nothing changed.
// GitHub returns 304 Not Modified for unchanged data, which is FREE (no rate limit cost).
let lastEtag = '';
let lastJobs = null;
let apiCalls = 0;
let cachedCalls = 0;
async function fetchJobs() {
const url = `GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs`;
const params = {
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
per_page: 100,
headers: {},
};
if (lastEtag) {
params.headers['if-none-match'] = lastEtag;
}
try {
const response = await github.request(url, params);
apiCalls++;
const rateRemaining = response.headers['x-ratelimit-remaining'] || '?';
const rateLimit = response.headers['x-ratelimit-limit'] || '?';
console.log(`[rate-limit] ${rateRemaining}/${rateLimit} remaining (ETag: ${lastEtag ? 'sent' : 'none'}) | this session: ${apiCalls} paid, ${cachedCalls} free`);
lastEtag = response.headers.etag || '';
const jobs = response.data.jobs;
// Handle pagination if >100 jobs
// ETag only covers page 1, so invalidate it to avoid stale cache
// when later pages change but page 1 doesn't.
if (response.data.total_count > 100) {
lastEtag = '';
for (let page = 2; page <= Math.ceil(response.data.total_count / 100); page++) {
const { data: pageData } = await github.request(url, {
...params,
page,
headers: {},
});
jobs.push(...pageData.jobs);
}
}
lastJobs = jobs;
return { jobs, cached: false };
} catch (err) {
if (err.status === 304 && lastJobs) {
cachedCalls++;
console.log(`[rate-limit] 304 Not Modified | this session: ${apiCalls} paid, ${cachedCalls} free`);
return { jobs: lastJobs, cached: true };
}
throw err;
}
}
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const { jobs, cached } = await fetchJobs();
let allCompleted = true;
let failedJobs = [];
let completedCount = 0;
let totalCount = 0;
for (const spec of normalizedSpecs) {
const matchingJobs = jobs.filter(job => matchesSpec(job.name, spec));
for (const job of matchingJobs) {
totalCount++;
if (!cached) {
console.log(`${job.name}: status=${job.status}, conclusion=${job.conclusion}`);
}
if (job.status === 'completed') {
completedCount++;
if (job.conclusion !== 'success' && job.conclusion !== 'skipped') {
failedJobs.push(job.name);
}
} else {
allCompleted = false;
}
}
if (matchingJobs.length < spec.expected_count) {
console.log(`${spec.prefix}: found ${matchingJobs.length}/${spec.expected_count} jobs (waiting for more)`);
allCompleted = false;
}
}
console.log(`[${stageName}] Progress: ${completedCount}/${totalCount} jobs completed (expected ${totalExpectedJobs})${cached ? ' (cached, no rate limit cost)' : ''}`);
// Fail fast if any jobs failed
if (failedJobs.length > 0) {
core.setOutput('result', 'failure');
core.setFailed(`${stageName} jobs failed: ${failedJobs.join(', ')}`);
return;
}
if (allCompleted && totalCount >= totalExpectedJobs) {
core.setOutput('result', 'success');
return;
}
console.log(`Waiting ${pollIntervalSeconds}s... (attempt ${attempt + 1}/${maxAttempts})`);
await new Promise(resolve => setTimeout(resolve, pollIntervalSeconds * 1000));
}
core.setFailed(`Timeout waiting for ${stageName} jobs`);
core.setOutput('result', 'timeout');