[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1,3 @@
__pycache__/
*.pyc
.state/
@@ -0,0 +1 @@
XingfenD
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
@@ -0,0 +1,122 @@
---
name: model-support-checker
description: Use when checking HuggingFace or ModelScope model support for SGLang, vLLM, or vLLM-Ascend, finding the first supporting version, or diagnosing "model not supported" errors.
---
# Model Support Checker
Check whether a HuggingFace / ModelScope model is supported by **SGLang**, **vLLM**, or **vLLM-Ascend**, and since which version.
## Methodology
The authoritative signal: does the framework's **source code** contain an implementation for the model's architecture string? Docs are supplementary.
1. **Architecture** — `architectures` from `config.json` (HF → ModelScope fallback). Override with `--arch`.
2. **Docs** (supplementary) — grep the framework's supported-models page.
3. **Source** (authoritative) — search the models directory for the architecture string.
4. **Reconcile** — if docs say YES but source says NO, manually inspect the framework's repo to resolve the discrepancy (see Step 4b).
5. **Version** — earliest commit on the implementation file → nearest release tag.
The tool is stateful; mode persists in `.state/state.json`. Local clone (recommended) greps on disk. Token mode uses GitHub API.
## Skill Workflow
**Step 1 — Doctor.** `python3 main.py --doctor`. `[ERROR]` → fix first, stop. `[WARN]` → ignore, continue.
**Step 2 — Setup (optional).** Only if no state exists. Recommend local: `python3 main.py --setup local`.
**Step 3 — Resolve model ID.** Infer full `<org>/<model_name>` from user input, matching the hosting platform convention. Parse URLs or short names. Query the web if uncertain.
**Step 4 — Run.**
```bash
python3 main.py [--framework <fw>] [--source <src>] [--arch <arch>] <model_id>
```
- All frameworks: `python3 main.py <model_id>`
- One framework: `python3 main.py --framework vllm <model_id>`
- Manual arch (no config.json needed): `python3 main.py --arch LlamaForCausalLM`
- Arch + model: `python3 main.py --framework vllm --arch DeepseekV3ForCausalLM <model_id>`
**Step 4b — Reconcile discrepancies.** If docs=YES but source=NO for any framework, the directory layout has likely changed. Do not assume known paths; instead:
1. Read the framework's registry file to find where the architecture is registered.
2. Extract the module path from the registry entry.
3. Trace that module path to locate the actual implementation file on disk.
4. Verify the architecture string exists in that file.
5. If the registry does not contain the architecture, search the entire framework repo for the architecture string (e.g. `rg <ArchName> <repo_root>`) to locate where it is referenced.
6. Update the result to YES with the correct file path.
**Step 5 — Report.** Parse Summary. Report: YES/NO per framework, minimum version, file path, vLLM category/module/class.
## Fallback Strategies
### Fallback 1: config.json fetch fails
Escalate in order:
1. **Switch platform** — retry with `--source hf` (default is modelscope).
2. **webfetch** — agent fetches `config.json` directly:
- HF: `https://huggingface.co/<org>/<model>/resolve/main/config.json`
- MS: `https://modelscope.cn/models/<org>/<model>/resolve/master/config.json`
Extract `architectures`, then: `python3 main.py --arch <ArchName> <model_id>`
3. **Guide user** — if webfetch fails (private/gated), provide links for the user to read `architectures` from `config.json`:
- HF: `https://huggingface.co/<org>/<model>/blob/main/config.json`
- MS: `https://modelscope.cn/models/<org>/<model>/files`
Then run with `--arch`.
### Fallback 2: Script cannot run
Bypass the script; follow methodology manually:
1. Get architecture (webfetch or ask user).
2. Search framework GitHub repos for the architecture string:
- vLLM (`vllm-project/vllm`): `vllm/model_executor/models/` or `vllm/models/<family>/`
- SGLang (`sgl-project/sglang`): `python/sglang/srt/models/`
- vLLM-Ascend (`vllm-project/vllm-ascend`): `vllm_ascend/models/` (may include subdirs like `minimax_m3/`)
3. Check official supported-models docs.
4. Earliest commit → nearest release tag for version.
5. Report in script Summary format.
### Fallback 3: Source=NO but model is recent / may ship support out-of-tree
The script's source check is authoritative for the **merged `main` branch checkout**, but framework teams frequently ship support through channels that are NOT in the default `main` checkout the script greps:
- **Official Docker images** with the implementation baked in (e.g. `lmsysorg/sglang:glm-5.3-flash`, `vllm/vllm-openai-rocm:glm53-flash`).
- **A separate support branch** not yet merged to main (e.g. `xinyuan/glm-5.3-flash-support`).
- **Vendor recipes / cookbook pages** (e.g. `recipes.vllm.ai/<org>/<model>`, `cookbook.sglang.io/...`).
This is the reverse of Step 4b (docs=YES, source=NO). When source=NO, do NOT report a flat "unsupported" until the official deployment docs are checked:
1. Fetch the model card on **both** HF and ModelScope (one is often gated/401):
- HF: `https://huggingface.co/<org>/<model>` and `.../resolve/main/README.md`
- MS: `https://modelscope.cn/models/<org>/<model>` and `.../resolve/master/README.md`
2. Find a "Deploy / Serve locally" section. If it lists vLLM / SGLang / vLLM-Ascend, the model IS deployable — capture the exact mechanism:
- Docker image tag(s) and required version (e.g. "vLLM 0.29.0+", "use docker before integration is in the public repo").
- Extra requirements: FlashInfer version, GPU arch (Hopper+/Blackwell/gfx950), `--tool-call-parser` / `--reasoning-parser`, MTP/speculative config.
- Caveats: features only on a specific branch/PR, image alone insufficient, etc.
3. Follow the recipe links to get the concrete launch command and image name.
4. Report a **nuanced** result instead of a bare NO. Distinguish these states:
- `merged` — in the public repo/PyPI, version-known.
- `image-only` — runs via vendor Docker image, not yet in public main.
- `branch-only` — needs a specific git branch/PR checkout.
- `unsupported` — no official path at all.
The official model card + recipes are authoritative about HOW to run, even when the implementation is not in the default `main` checkout. This supersedes a flat NO from the script.
## Quick Reference
| Flag | Description |
|------|-------------|
| `--framework` | `sglang` / `vllm` / `vllm-ascend` / `all` |
| `--source` | `auto` / `hf` / `modelscope` |
| `--arch` | Manual arch name(s); skips config.json |
| `--doctor` | Check setup state |
| `--setup` | `local` or `token` |
`python3 main.py --help` for full flag list.
## Common Mistakes
- **Skipping doctor** — always `--doctor` first.
- **Giving up on config.json failure** — escalate: switch platform → webfetch → guide user.
- **Not using `--arch`** — when config.json is unreachable, pass `--arch` instead of aborting.
- **Trusting source=NO when docs=YES** — frameworks may reorganize directories; do Step 4b reconciliation.
- **Trusting source=NO as final for recent models** — vendors often ship support via official Docker images / separate branches / recipe pages that are NOT in the default `main` checkout the script greps. Check the official model card + recipes (Fallback 3) before concluding "unsupported".
@@ -0,0 +1,131 @@
# model-support-checker
Check whether a HuggingFace / ModelScope model is supported by **SGLang**,
**vLLM**, or **vLLM-Ascend**, and since which version.
## Setup (one-time)
The tool is **stateful**: your access mode is persisted in `.state/state.json`
(gitignored), so later runs need no flags or tokens.
On first run you choose between two access modes — **local clone is recommended**:
| | Local clone (recommended) | GitHub PAT |
|---|---|---|
| Result quality | Definitive (files grepped on disk) | Best-effort without token; code search 403s anonymously |
| Rate limits | None | Yes — can break batch/version checks |
| Repeat checks | Fast (disk reads) | Slower (GitHub API) |
| Works offline | Yes, after initial clone | No |
| Cost | ~1 GB+ disk (vLLM full history needed) | None |
| Freshness | Stale until `git pull` | Always current |
```bash
python3 main.py --setup local # clone all repos into .state/repos/
python3 main.py --setup local --vllm-path P1 --sglang-path P2 --vllm-ascend-path P3 # reuse existing checkouts
python3 main.py --setup token # GitHub API mode
python3 main.py --doctor # check setup state and local checkouts
python3 main.py --reset-state # forget setup (cloned repos are kept and reused)
```
Notes:
- Local clones are full clones (`--depth 1` breaks version detection).
- The GitHub token is NEVER written to `.state/`; in token mode export
`GITHUB_TOKEN` per run.
- Re-run `--setup <mode>` anytime to switch modes; explicit `--vllm-path` /
`--sglang-path` / `--vllm-ascend-path` / `--token` flags override the saved state for that run.
- Each run in local mode refreshes checkouts in the background (`git fetch`)
and reports staleness or failures at the end — never blocking the main
check.
## Methodology
For each framework the checker runs four framework-agnostic steps:
1. **Architecture name** — read `architectures` from the model's `config.json`
(HuggingFace first, ModelScope API as fallback). Can be overridden with
`--arch` to skip the network lookup entirely.
2. **Official docs** (supplementary) — grep the framework's supported-models page.
3. **GitHub source** (authoritative) — search the framework's models directory on
GitHub. All frameworks register a model by mapping the HF architecture
string to a class in `<models_dir>/<file>.py`, so the architecture string is
always present in its implementation file.
- For vLLM this step additionally parses `registry.py` to extract the model
category, module path, class name, and checks
`_PREVIOUSLY_SUPPORTED_MODELS` / `_OOT_SUPPORTED_MODELS`.
- For vLLM-Ascend this step parses `__init__.py` for `ModelRegistry.register_model()`
calls to extract module and class.
4. **Version** — determine the first framework release that contains the
implementation (via the file's earliest commit date → nearest release).
The authoritative "is it supported" check uses the GitHub **code-search API**
with a path filter on the models directory. Anonymous code search returns 403,
so a `GITHUB_TOKEN` is required for a definitive answer. Without a token the
tool falls back to scraping the models directory tree + grepping candidate files
on the raw CDN; that fallback can be incomplete, so a NO without a token is not
definitive. Step 4 also needs a token on rate-limited IPs.
### Local checkouts (no GitHub API)
Local mode is the recommended setup (`--setup local` clones all repos into
`.state/repos/` automatically). All source reads (models directory,
`registry.py`, and version history) are served from disk via `git`, and **no
GitHub API requests are made** — only the architecture lookup (step 1) and the
docs check (step 2) still touch the network.
```bash
# Override saved paths for a single run (no GITHUB_TOKEN needed for source/registry/version)
python3 main.py --framework vllm --vllm-path /path/to/vllm deepseek-ai/DeepSeek-V3
python3 main.py --framework sglang --sglang-path /path/to/sglang Qwen/Qwen3.6-35B-A3B
python3 main.py --framework vllm-ascend --vllm-ascend-path /path/to/vllm-ascend deepseek-ai/DeepSeek-V3
```
`--vllm-ref` is ignored when `--vllm-path` is given. The local path must be a
git checkout of the framework repo (so `git log`/`git tag` are available for
version detection). Keep checkouts fresh with an occasional `git pull`.
No third-party dependencies — standard library only.
## Usage
```bash
# After setup, just pass a model id
python3 main.py --framework sglang Qwen/Qwen3.6-35B-A3B
python3 main.py --framework vllm deepseek-ai/DeepSeek-V3
python3 main.py --framework vllm-ascend deepseek-ai/DeepSeek-V3
# Check all frameworks at once (default)
python3 main.py meta-llama/Llama-3.1-8B
# Token mode: GITHUB_TOKEN per run (never stored)
GITHUB_TOKEN=xxx python3 main.py meta-llama/Llama-3.1-8B
# Manually specify architecture name (skip config.json fetch)
python3 main.py --arch LlamaForCausalLM
python3 main.py --framework vllm --arch DeepseekV3ForCausalLM
python3 main.py --framework vllm --arch "DeepseekV3ForCausalLM,DeepseekV2ForCausalLM" some-org/some-model
```
### Options
| Flag | Description |
| -------------------- | ------------------------------------------------------------------------------------------------- |
| `model_id` | HuggingFace or ModelScope model id (positional; optional when `--arch` is given). |
| `--setup` | One-time setup: `local` (clone repos into `.state/repos/`, recommended) or `token`; persists to `.state/state.json`. |
| `--reset-state` | Forget saved setup state (cloned repos kept). |
| `--doctor` | Check setup state and local checkouts, then exit. |
| `--framework` | `sglang`, `vllm`, `vllm-ascend`, or `all` (default: `all`). |
| `--source` | `auto`, `hf`, or `modelscope` for reading `config.json`. |
| `--arch` | Manually specify architecture name(s), comma-separated. Skips config.json fetch entirely. |
| `--token` | GitHub token (or set `GITHUB_TOKEN` env); overrides saved state for this run. |
| `--no-docs` | Skip the docs check. |
| `--vllm-ref` | vLLM git ref (branch/tag) for the registry check (default: `main`). |
| `--vllm-path` | Local vllm repo checkout; overrides saved path for this run. |
| `--sglang-path` | Local sglang repo checkout; overrides saved path for this run. |
| `--vllm-ascend-path` | Local vllm-ascend repo checkout; overrides saved path for this run. |
| `-v, --verbose` | Verbose output. |
## License
[The Unlicense](../LICENSE) — released into the public domain.
@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Entry point for model-support-checker.
Usage:
python3 main.py --setup local # first run (recommended)
python3 main.py --doctor # check setup state
GITHUB_TOKEN=xxx python3 main.py --framework sglang Qwen/Qwen3.6-35B-A3B
python3 main.py --framework vllm deepseek-ai/DeepSeek-V3 # after setup
python3 main.py --framework vllm-ascend deepseek-ai/DeepSeek-V3
python3 main.py --arch LlamaForCausalLM # manual architecture
"""
import sys
from src.cli import main
if __name__ == "__main__":
try:
main()
except RuntimeError as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
@@ -0,0 +1 @@
"""model-support-checker package: SGLang/vLLM model support checker."""
@@ -0,0 +1,59 @@
"""Step 1: resolve a model's architecture name from its config.json."""
import json
from .http_utils import _get, _get_json
def get_architecture(model_id, source="modelscope", arch_override=None):
"""Return (architectures: list[str], source_used: str).
If *arch_override* is given (comma-separated architecture string), return it
directly without fetching config.json. This allows the caller to bypass
the network lookup when the config is unavailable or the user already knows
the architecture name.
"""
if arch_override:
archs = [a.strip() for a in arch_override.split(",") if a.strip()]
if archs:
return archs, "manual"
if source in ("auto", "hf"):
url = f"https://huggingface.co/{model_id}/resolve/main/config.json"
try:
body, _ = _get(url)
except RuntimeError:
body = None
if body:
try:
data = json.loads(body)
if data.get("architectures"):
return data["architectures"], "huggingface"
except json.JSONDecodeError:
pass
if source in ("auto", "modelscope"):
url = f"https://modelscope.cn/models/{model_id}/resolve/master/config.json"
try:
body, _ = _get(url)
except RuntimeError:
body = None
if body:
try:
data = json.loads(body)
if data.get("architectures"):
return data["architectures"], "modelscope"
except json.JSONDecodeError:
pass
api = f"https://modelscope.cn/api/v1/models/{model_id}"
data = _get_json(api)
if data:
arch = data.get("Architectures") or data.get("architectures")
if arch:
return [arch] if isinstance(arch, str) else arch, "modelscope-api"
raise RuntimeError(
f"Could not fetch config.json for '{model_id}' from HuggingFace or ModelScope.\n"
f"Try: --source hf | --source modelscope (pick a specific platform)\n"
f" or: --arch <ArchitectureName> (e.g. --arch LlamaForCausalLM)"
)
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
"""Check whether a HuggingFace / ModelScope model is supported by SGLang, vLLM, or vLLM-Ascend,
and since which version.
Usage:
python3 main.py --setup local # first run: clone repos into .state/repos/ (recommended)
python3 main.py --setup token # first run: use GitHub API (GITHUB_TOKEN per run)
python3 main.py --doctor # check setup state and local checkouts
GITHUB_TOKEN=xxx python3 main.py --framework sglang Qwen/Qwen3.6-35B-A3B
python3 main.py --framework vllm deepseek-ai/DeepSeek-V3 # after setup
python3 main.py --framework vllm-ascend deepseek-ai/DeepSeek-V3
python3 main.py --arch LlamaForCausalLM # manual architecture (skip config.json fetch)
"""
import argparse
import os
import sys
import time
from . import state
from .architecture import get_architecture
from .framework_strategies import STRATEGIES
# Max seconds to wait for background checkout refreshes before exiting.
_REFRESH_JOIN_SECONDS = 15
# Framework names that can be used with --framework
FRAMEWORK_CHOICES = ["sglang", "vllm", "vllm-ascend", "all"]
def main():
ap = argparse.ArgumentParser(description="Check SGLang/vLLM/vLLM-Ascend model support.")
ap.add_argument("model_id", nargs="?", help="e.g. Qwen/Qwen3.6-35B-A3B (optional with --arch)")
ap.add_argument("--framework", choices=FRAMEWORK_CHOICES, default="all",
help="which framework to check (default: all)")
ap.add_argument("--source", choices=["auto", "hf", "modelscope"],
default="modelscope", help="where to read config.json")
ap.add_argument("--arch", default=None,
help="manually specify architecture name(s), comma-separated "
"(e.g. LlamaForCausalLM). Skips config.json fetch.")
ap.add_argument("--token", default=os.environ.get("GITHUB_TOKEN"),
help="GitHub token (or set GITHUB_TOKEN env)")
ap.add_argument("--no-docs", action="store_true", help="skip docs check")
ap.add_argument("--vllm-ref", default=None,
help="vLLM git ref (branch/tag) for registry check (default: main)")
ap.add_argument("--vllm-path", default=None,
help="local vllm repo checkout path (skip GitHub API)")
ap.add_argument("--sglang-path", default=None,
help="local sglang repo checkout path (skip GitHub API)")
ap.add_argument("--vllm-ascend-path", default=None,
help="local vllm-ascend repo checkout path (skip GitHub API)")
ap.add_argument("--setup", choices=["local", "token"], default=None,
help="first-run setup: 'local' clones repos (recommended), "
"'token' uses the GitHub API. Saved to .state/state.json.")
ap.add_argument("--reset-state", action="store_true",
help="forget saved setup state")
ap.add_argument("--doctor", action="store_true",
help="check setup state and exit")
ap.add_argument("-v", "--verbose", action="store_true")
args = ap.parse_args()
if args.reset_state:
state.reset()
if not args.setup:
return
if args.setup:
state.ensure_setup(args.setup, args)
return
# Resolve setup state: first run asks the user (TTY menu) or tells the
# caller to run --setup. With --doctor we skip the interactive menu and
# just report whatever is (or isn't) on disk.
st = state.load()
if st is None and args.doctor:
has_errors = state.print_report(state.doctor(st, args, framework=args.framework))
sys.exit(1 if has_errors else 0)
if st is None:
if sys.stdin.isatty():
state.ensure_setup(state.first_run_menu(), args)
st = state.load()
else:
raise RuntimeError(
"First run: no setup found (.state/state.json missing). Ask the "
"user to choose, then run one of:\n"
" python3 main.py --setup local # recommended\n"
" python3 main.py --setup local --vllm-path P1 --sglang-path P2 --vllm-ascend-path P3\n"
" python3 main.py --setup token # needs GITHUB_TOKEN"
)
# The agent is expected to run `python3 main.py --doctor` explicitly
# before any model check (see SKILL.md workflow). When --doctor is passed
# directly, we inspect state and exit without touching a model.
if args.doctor:
issues = state.doctor(st, args, framework=args.framework)
has_errors = state.print_report(issues)
sys.exit(1 if has_errors else 0)
# Build path mapping: framework name -> local path
paths = {
"vllm": args.vllm_path or st.get("vllm_path"),
"sglang": args.sglang_path or st.get("sglang_path"),
"vllm-ascend": args.vllm_ascend_path or st.get("vllm_ascend_path"),
}
if not args.model_id and not args.arch:
ap.error("model_id is required (unless --arch is specified)")
frameworks = list(STRATEGIES) if args.framework == "all" else [args.framework]
# Local mode: refresh checkouts in the background while checking runs;
# failures/staleness are only reported at the very end, never blocking.
local_paths = {}
for fw_name in frameworks:
lp = paths.get(fw_name)
if lp and os.path.isdir(os.path.join(lp, ".git")):
local_paths[fw_name] = lp
freshness, refresh_threads = state.start_refresh(local_paths)
# Step 1 (framework-independent): architecture name.
archs, src = get_architecture(args.model_id or "", args.source,
arch_override=args.arch)
arch = archs[0]
model_display = args.model_id or "(manual)"
print(f"Model: {model_display}")
print(f"[1] Architecture(s): {', '.join(archs)} (source: {src})\n")
results = {}
for fw_name in frameworks:
local_path = paths.get(fw_name)
strategy = STRATEGIES[fw_name]
ctx = strategy.make_context(local_path)
print(f"========== {strategy.label} ==========")
if ctx.is_local:
print(f" (using local checkout: {ctx.local_dir})")
# Step 3 (authoritative)
print("[3] GitHub source check (authoritative)...")
supported, file_path = strategy.check_support(arch, ctx, args.token, args.verbose)
print(f" Supported in source: {'YES' if supported else 'NO'}"
+ (f" ({file_path})" if file_path else ""))
# Framework-specific extra checks (e.g. vLLM registry)
extra = strategy.extra_checks(arch, ctx, args.token, args.verbose, ref=args.vllm_ref)
# Step 2 (docs, supplementary)
docs = "skipped"
if not args.no_docs:
print("[2] Official docs check...")
docs = strategy.check_docs(arch, args.verbose)
print(f" Docs mention: {docs}")
# Step 4 (version)
version = None
if supported:
print(f"[4] Determining first supporting {strategy.label} version...")
version = strategy.get_version(strategy.version_path(file_path), ctx, args.token, args.verbose)
print(f" First version: {version}")
else:
print("[4] Skipped (not found in source).")
results[fw_name] = (supported, file_path, version, docs, extra, strategy)
print()
# Final combined summary.
print("=== Summary ===")
print(f" Model : {model_display}")
print(f" Architecture : {arch}")
for fw_name in frameworks:
supported, file_path, version, docs, extra, strategy = results[fw_name]
line = f" {strategy.label:12} supported : {'YES' if supported else 'NO'}"
if supported:
line += f" | since {version} | {file_path}"
print(line)
strategy.format_summary_extra(extra)
print("\nNote: GitHub source is the authoritative signal for all frameworks.")
print("The 'since version' is approximated from the implementation file's first")
print("commit date and may be off by a release or two; it needs GITHUB_TOKEN")
print("on rate-limited IPs.")
_print_refresh_notes(freshness, refresh_threads, args.verbose)
def _print_refresh_notes(freshness, threads, verbose=False):
"""Report background checkout refresh results after the main flow.
Waits at most _REFRESH_JOIN_SECONDS total so a slow fetch never blocks
the exit for long; unfinished workers are simply not reported.
"""
deadline = time.monotonic() + _REFRESH_JOIN_SECONDS
for t in threads:
t.join(timeout=max(0.0, deadline - time.monotonic()))
notes = []
for fw_name, info in sorted(freshness.items()):
label = STRATEGIES[fw_name].label
if "error" in info:
notes.append(f"{label}: checkout refresh failed ({info['error']}); "
f"results reflect the checkout as-is.")
elif info.get("behind"):
notes.append(f"{label}: checkout is {info['behind']} commit(s) behind "
f"its upstream — update with: git -C {info['path']} pull")
elif verbose and not info.get("no_upstream"):
notes.append(f"{label}: checkout is up to date.")
if notes:
print("\n=== Checkout freshness ===")
for n in notes:
print(f" NOTE: {n}")
if __name__ == "__main__":
try:
main()
except RuntimeError as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
@@ -0,0 +1,67 @@
"""Execution context for framework checks.
Replaces the global mutable state in config.py. Each check runs with its own
Context, making the code concurrency-safe and easier to test.
"""
import os
from dataclasses import dataclass
from typing import Optional
from .http_utils import _get
@dataclass
class Context:
"""Immutable context for a single framework check."""
repo: str
branch: str
models_dir: str
docs_url: str
label: str
local_dir: Optional[str] = None
@property
def api(self) -> str:
return f"https://api.github.com/repos/{self.repo}"
@property
def raw(self) -> str:
return f"https://raw.githubusercontent.com/{self.repo}/{self.branch}"
@property
def is_local(self) -> bool:
return self.local_dir is not None
def read_source(self, rel_path: str) -> Optional[str]:
"""Read a repository-relative file.
Local mode: read from disk.
Remote mode: fetch from GitHub raw CDN.
Returns None on failure.
"""
if self.is_local:
p = os.path.join(self.local_dir, rel_path)
try:
with open(p, encoding="utf-8") as f:
return f.read()
except OSError:
return None
try:
body, _ = _get(f"{self.raw}/{rel_path}")
except RuntimeError:
return None
return body
@classmethod
def from_strategy(cls, strategy, local_dir: Optional[str] = None) -> "Context":
"""Create a Context from a strategy instance."""
return cls(
repo=strategy.repo,
branch=strategy.branch,
models_dir=strategy.models_dir,
docs_url=strategy.docs_url,
label=strategy.label,
local_dir=os.path.expanduser(local_dir) if local_dir else None,
)
@@ -0,0 +1,10 @@
"""Framework strategies: each framework is a strategy instance."""
from .base import FrameworkStrategy
from .sglang import SglangStrategy
from .vllm import VllmStrategy
from .vllm_ascend import VllmAscendStrategy
STRATEGIES = {s.name: s() for s in (VllmStrategy, SglangStrategy, VllmAscendStrategy)}
__all__ = ["FrameworkStrategy", "STRATEGIES"]
@@ -0,0 +1,249 @@
"""Base strategy class for framework-specific behavior."""
import json
import os
import re
import subprocess
import urllib.parse
from typing import Optional, Tuple
from ..context import Context
from ..http_utils import BROWSER_UA, _get, _get_json
class FrameworkStrategy:
name = None
label = None
repo = None
branch = None
models_dir = None
docs_url = None
def make_context(self, local_dir: Optional[str] = None) -> Context:
return Context.from_strategy(self, local_dir)
def check_docs(self, arch: str, verbose: bool = False) -> str:
body, _ = _get(self.docs_url)
if not body:
if verbose:
print(f" [docs] could not fetch {self.docs_url} (may be JS-rendered).")
return "unknown"
if arch in body or arch.lower() in body.lower():
return "yes"
family = re.split(r"(?=[A-Z])", arch)[0].lower()
if family and family in body.lower():
return "maybe"
return "no"
def check_support(
self, arch: str, ctx: Context, token: Optional[str] = None, verbose: bool = False
) -> Tuple[bool, Optional[str]]:
if ctx.is_local:
return self._check_local(arch, ctx, verbose)
if token:
inner = "^" + ctx.models_dir.replace("/", r"\/") + r"\/"
q = f"{arch} repo:{ctx.repo} path:/{inner}/"
url = f"{ctx.api}/search/code?q={urllib.parse.quote(q)}&per_page=20"
data = _get_json(url, token)
if data:
for item in data.get("items", []):
if item["path"].startswith(ctx.models_dir) and item["path"].endswith(".py"):
if verbose:
print(f" [github] code-search hit: {item['path']}")
return True, item["path"]
elif verbose:
print(" [github] code search returned nothing / was rate-limited.")
if not token and verbose:
print(" [github] no token: using best-effort tree scrape "
"(may be incomplete; set GITHUB_TOKEN for a definitive answer).")
files = self.list_model_files(ctx, verbose)
candidates = self.match_files(arch, files) if files else []
if verbose:
print(" [github] fallback candidates: " + ", ".join(candidates) or "(none)")
for fname in candidates:
path = f"{ctx.models_dir}/{fname}"
body = ctx.read_source(path)
if body and arch in body:
if verbose:
print(f" [github] '{arch}' found in {path}")
return True, path
return False, None
def get_version(
self, file_path: str, ctx: Context, token: Optional[str] = None, verbose: bool = False
) -> Optional[str]:
if not file_path:
return None
if ctx.is_local:
return self._get_version_local(file_path, ctx, verbose)
try:
date = self._earliest_commit_date(file_path, ctx, token)
except RuntimeError as e:
return f"unknown ({e}; try GITHUB_TOKEN)"
if verbose:
print(f" [version] earliest commit touching {file_path}: {date}")
if not date:
return "unknown"
try:
return self._first_release_after(date, ctx, token)
except RuntimeError as e:
return f"unknown ({e}; try GITHUB_TOKEN)"
def version_path(self, file_path: Optional[str]) -> Optional[str]:
return file_path
def extra_checks(self, arch: str, ctx: Context, token: Optional[str], verbose: bool, ref: Optional[str] = None):
return None
def format_summary_extra(self, info) -> None:
pass
def list_model_files(self, ctx: Context, verbose: bool = False) -> Optional[set]:
if ctx.is_local:
d = os.path.join(ctx.local_dir, ctx.models_dir)
try:
names = {f for f in os.listdir(d) if f.endswith(".py")}
except OSError:
if verbose:
print(f" [local] could not list models directory {d}.")
return None
names.discard("__init__.py")
if names:
if verbose:
print(f" [local] listed {len(names)} model files from {d}.")
return names
return None
url = f"{ctx.api}/contents/{ctx.models_dir}"
data = _get_json(url)
if data:
names = {
e["name"]
for e in data
if e.get("type") == "file" and e["name"].endswith(".py")
}
names.discard("__init__.py")
if names:
if verbose:
print(f" [github] listed {len(names)} model files via Contents API.")
return names
if verbose:
print(" [github] Contents API unavailable; scraping tree page.")
turl = f"https://github.com/{ctx.repo}/tree/{ctx.branch}/{ctx.models_dir}"
try:
body, _ = _get(turl, ua=BROWSER_UA)
except RuntimeError:
body = None
if not body:
if verbose:
print(" [github] could not fetch models directory listing.")
return None
names = set(re.findall(r'"name":"([a-zA-Z0-9_]+\.py)"', body))
names.discard("__init__.py")
return names
@staticmethod
def match_files(arch: str, files: set) -> list:
a = arch.lower().replace("_", "")
fam = re.match(r"[A-Za-z]+", arch)
fam = fam.group(0).lower() if fam else ""
out = []
for f in files:
if not f.endswith(".py"):
continue
stem = f[:-3].lower().replace("_", "")
if (
a.startswith(stem)
or stem.startswith(a)
or (fam and (stem.startswith(fam) or fam.startswith(stem)))
):
out.append(f)
return sorted(out, key=len)
def _check_local(self, arch: str, ctx: Context, verbose: bool = False) -> Tuple[bool, Optional[str]]:
files = self.list_model_files(ctx, verbose)
candidates = self.match_files(arch, files) if files else []
if verbose:
print(" [local] candidates: " + (", ".join(candidates) or "(none)"))
for fname in candidates:
path = f"{ctx.models_dir}/{fname}"
body = ctx.read_source(path)
if body and arch in body:
if verbose:
print(f" [local] '{arch}' found in {path}")
return True, path
return False, None
def _earliest_commit_date(self, path: str, ctx: Context, token: Optional[str]) -> Optional[str]:
headers = {"Authorization": f"Bearer {token}"} if token else {}
url = f"{ctx.api}/commits?path={urllib.parse.quote(path)}&per_page=1"
try:
body, hdrs = _get(url, headers)
except RuntimeError as e:
raise RuntimeError(str(e)) from e
if not body:
return None
link = hdrs.get("Link", "")
last_page = 1
for part in link.split(","):
if 'rel="last"' in part:
m = re.search(r"[?&]page=(\d+)", part)
if m:
last_page = int(m.group(1))
if last_page > 1:
try:
body, _ = _get(f"{url}&page={last_page}", headers)
except RuntimeError:
body = None
if not body:
return None
commits = json.loads(body)
if not commits:
return None
commit = commits[-1]["commit"]
return commit.get("committer", {}).get("date") or commit.get("author", {}).get("date")
def _first_release_after(self, date_iso: str, ctx: Context, token: Optional[str]) -> str:
rels = _get_json(f"{ctx.api}/releases?per_page=100", token) or []
picked = None
for r in rels:
pub = r.get("published_at")
if not pub:
continue
if pub >= date_iso and (picked is None or pub < picked["published_at"]):
picked = r
return picked["tag_name"] if picked else "unreleased (main)"
def _get_version_local(self, file_path: str, ctx: Context, verbose: bool = False) -> str:
repo = ctx.local_dir
log = subprocess.run(
["git", "-C", repo, "log", "--reverse", "--format=%aI", "--", file_path],
capture_output=True, text=True,
)
if log.returncode != 0 or not log.stdout.strip():
if verbose:
print(f" [version] no git history for {file_path}.")
return "unknown"
first_date = log.stdout.strip().splitlines()[0]
if verbose:
print(f" [version] earliest commit touching {file_path}: {first_date}")
tags = subprocess.run(
["git", "-C", repo, "for-each-ref", "--sort=creatordate",
"--format=%(creatordate:iso-strict) %(refname:short)", "refs/tags"],
capture_output=True, text=True,
)
if tags.returncode != 0:
return "unreleased (main)"
picked = None
for line in tags.stdout.splitlines():
line = line.strip()
if not line:
continue
date, tag = line.split(" ", 1)
if date >= first_date and (picked is None or date < picked[0]):
picked = (date, tag)
return picked[1] if picked else "unreleased (main)"
@@ -0,0 +1,12 @@
"""SGLang strategy."""
from .base import FrameworkStrategy
class SglangStrategy(FrameworkStrategy):
name = "sglang"
label = "SGLang"
repo = "sgl-project/sglang"
branch = "main"
models_dir = "python/sglang/srt/models"
docs_url = "https://docs.sglang.io/docs/supported-models"
@@ -0,0 +1,10 @@
"""vLLM framework strategy."""
from .registry import (
check_vllm_registry,
fetch_vllm_registry,
parse_vllm_registry,
)
from .strategy import VllmStrategy
__all__ = ["VllmStrategy", "fetch_vllm_registry", "parse_vllm_registry", "check_vllm_registry"]
@@ -0,0 +1,203 @@
"""vLLM registry.py source-level parsing (category, module, class, etc.)."""
import os
import re
from typing import Optional
from ...context import Context
from ...http_utils import _get
_REGISTRY_PATH = "vllm/model_executor/models/registry.py"
_CATEGORY_MAP = {
"_TEXT_GENERATION_MODELS": "text_generation",
"_EMBEDDING_MODELS": "embedding",
"_LATE_INTERACTION_MODELS": "late_interaction",
"_REWARD_MODELS": "reward",
"_TOKEN_CLASSIFICATION_MODELS": "token_classification",
"_SEQUENCE_CLASSIFICATION_MODELS": "sequence_classification",
"_MULTIMODAL_MODELS": "multimodal",
"_SPECULATIVE_DECODING_MODELS": "speculative_decoding",
"_TRANSFORMERS_SUPPORTED_MODELS": "transformers_supported",
"_TRANSFORMERS_BACKEND_MODELS": "transformers_backend",
}
_BROWSER_UA = {
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
}
def _parse_dict_entries(source, dict_name):
"""Extract architecture keys from a Python dict literal in source.
Returns dict[arch_name] = (module_str, class_str).
"""
pattern = rf"^{re.escape(dict_name)}\s*=\s*\{{"
match = re.search(pattern, source, re.MULTILINE)
if not match:
return {}
start = match.end()
depth = 1
i = start
while i < len(source) and depth > 0:
ch = source[i]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
elif ch in ("'", '"'):
quote = ch
i += 1
while i < len(source):
if source[i] == "\\":
i += 2
continue
if source[i] == quote:
break
i += 1
i += 1
body = source[start : i - 1]
entry_re = re.compile(
r'''["'](\w+)["']\s*:\s*\(\s*["']([^"']+)["']\s*,\s*["']([^"']+)["']\s*,?\s*\)'''
)
result = {}
for m in entry_re.finditer(body):
result[m.group(1)] = (m.group(2), m.group(3))
return result
def _parse_simple_dict(source, dict_name):
"""Extract entries from a simple dict: "Key": "Value"."""
pattern = rf"^{re.escape(dict_name)}\s*=\s*\{{"
match = re.search(pattern, source, re.MULTILINE)
if not match:
return {}
start = match.end()
depth = 1
i = start
while i < len(source) and depth > 0:
ch = source[i]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
elif ch in ("'", '"'):
quote = ch
i += 1
while i < len(source):
if source[i] == "\\":
i += 2
continue
if source[i] == quote:
break
i += 1
i += 1
body = source[start : i - 1]
entry_re = re.compile(r'''["'](\w+)["']\s*:\s*["']([^"']+)["']''')
result = {}
for m in entry_re.finditer(body):
result[m.group(1)] = m.group(2)
return result
def fetch_vllm_registry(ctx: Context, ref: Optional[str] = None, verbose: bool = False) -> str:
"""Fetch vLLM registry.py source.
In local mode (ctx.is_local) the file is read from disk; otherwise
it is fetched from the GitHub raw CDN.
"""
if ctx.is_local:
p = os.path.join(ctx.local_dir, _REGISTRY_PATH)
if verbose:
print(f" [registry] reading local {p}")
try:
with open(p, encoding="utf-8") as f:
body = f.read()
except OSError:
raise RuntimeError(f"Could not read local registry.py at {p}")
if not body:
raise RuntimeError(f"Empty local registry.py at {p}")
return body
ref = ref or ctx.branch
url = f"https://raw.githubusercontent.com/{ctx.repo}/{ref}/{_REGISTRY_PATH}"
if verbose:
print(f" [registry] fetching {url}")
body, _ = _get(url, ua=_BROWSER_UA)
if not body:
raise RuntimeError(f"Could not fetch registry.py from {url}")
return body
def parse_vllm_registry(source, verbose: bool = False):
"""Parse registry.py source and return structured info.
Returns:
supported: dict[arch] -> {category, module, class_name}
previously: dict[arch] -> version_str
oot: dict[arch] -> plugin_url
"""
supported = {}
for dict_name, category in _CATEGORY_MAP.items():
entries = _parse_dict_entries(source, dict_name)
for arch, (mod, cls) in entries.items():
supported[arch] = {
"category": category,
"module": mod,
"class_name": cls,
}
if verbose and entries:
print(f" [registry] {dict_name}: {len(entries)} entries")
previously = _parse_simple_dict(source, "_PREVIOUSLY_SUPPORTED_MODELS")
if verbose and previously:
print(f" [registry] _PREVIOUSLY_SUPPORTED_MODELS: {len(previously)} entries")
oot = _parse_simple_dict(source, "_OOT_SUPPORTED_MODELS")
if verbose and oot:
print(f" [registry] _OOT_SUPPORTED_MODELS: {len(oot)} entries")
return supported, previously, oot
def check_vllm_registry(arch, supported, previously, oot):
"""Check an architecture against parsed vLLM registry data.
Returns a dict with status details.
"""
if arch in supported:
info = supported[arch]
mod = info["module"]
if mod.startswith("vllm."):
full_module = mod
else:
full_module = f"vllm.model_executor.models.{mod}"
return {
"status": "supported",
"category": info["category"],
"module": full_module,
"class_name": info["class_name"],
}
if arch in previously:
return {
"status": "previously_supported",
"last_version": previously[arch],
}
if arch in oot:
return {
"status": "oot_plugin",
"plugin_url": oot[arch],
}
return {"status": "unsupported"}
@@ -0,0 +1,119 @@
"""vLLM strategy: registry.py parsing, version path, summary formatting."""
import os
from typing import Optional, Tuple
from ...context import Context
from .registry import (
check_vllm_registry,
fetch_vllm_registry,
parse_vllm_registry,
)
from ..base import FrameworkStrategy
_NEW_MODELS_DIR = "vllm/models"
class VllmStrategy(FrameworkStrategy):
name = "vllm"
label = "vLLM"
repo = "vllm-project/vllm"
branch = "main"
models_dir = "vllm/model_executor/models"
docs_url = "https://docs.vllm.ai/en/stable/models/supported_models/"
def __init__(self):
super().__init__()
self._registry_file_path = None
def check_support(
self, arch: str, ctx: Context, token: Optional[str] = None, verbose: bool = False
) -> Tuple[bool, Optional[str]]:
supported, file_path = super().check_support(arch, ctx, token, verbose)
if supported:
return True, file_path
try:
reg_source = fetch_vllm_registry(ctx=ctx, ref=self.branch, verbose=verbose)
reg_supported, _, _ = parse_vllm_registry(reg_source, verbose=verbose)
except RuntimeError as e:
if verbose:
print(f" [registry-fallback] skipped: {e}")
return False, None
if arch not in reg_supported:
return False, None
module = reg_supported[arch]["module"]
if not module.startswith("vllm.models."):
return False, None
resolved = self._resolve_new_style_path(module, arch, ctx, verbose)
if resolved:
self._registry_file_path = resolved
if verbose:
print(f" [registry-fallback] found via registry: {resolved}")
return True, resolved
return False, None
def _resolve_new_style_path(self, module, arch, ctx, verbose):
pkg_rel = module.replace(".", os.sep)
candidates = [
f"{pkg_rel}/__init__.py",
f"{pkg_rel}.py",
]
for rel in candidates:
body = ctx.read_source(rel)
if body and arch in body:
return rel
if ctx.is_local:
pkg_dir = os.path.join(ctx.local_dir, pkg_rel)
if os.path.isdir(pkg_dir):
for fname in sorted(os.listdir(pkg_dir)):
if fname.endswith(".py") and fname != "__init__.py":
fpath = os.path.join(pkg_dir, fname)
try:
with open(fpath, encoding="utf-8") as f:
body = f.read()
except OSError:
continue
if arch in body:
return f"{pkg_rel}/{fname}"
return None
def extra_checks(self, arch: str, ctx: Context, token: Optional[str], verbose: bool, ref: Optional[str] = None):
"""Parse registry.py for detailed vLLM model info."""
try:
reg_source = fetch_vllm_registry(ctx=ctx, ref=ref or self.branch, verbose=verbose)
reg_supported, reg_previously, reg_oot = parse_vllm_registry(
reg_source, verbose=verbose
)
print(f" Registry parsed: {len(reg_supported)} architectures registered")
info = check_vllm_registry(arch, reg_supported, reg_previously, reg_oot)
status = info["status"]
if status == "supported":
print(f" Category : {info['category']}")
print(f" Module : {info['module']}")
print(f" Class : {info['class_name']}")
elif status == "previously_supported":
print(f" PREVIOUSLY SUPPORTED (removed)")
print(f" Last version: v{info['last_version']}")
elif status == "oot_plugin":
print(f" OUT-OF-TREE PLUGIN REQUIRED")
print(f" Plugin: {info['plugin_url']}")
return info
except RuntimeError as e:
if verbose:
print(f" [registry] skipped: {e}")
return None
def version_path(self, file_path: Optional[str]) -> Optional[str]:
"""Prefer registry-resolved path, then file_path, then registry.py."""
return self._registry_file_path or file_path or f"{self.models_dir}/registry.py"
def format_summary_extra(self, info) -> None:
if not info:
return
status = info["status"]
if status == "previously_supported":
print(f" NOTE: removed after v{info['last_version']}")
elif status == "oot_plugin":
print(f" NOTE: requires plugin {info['plugin_url']}")
elif status == "supported":
print(f" Category: {info['category']}")
@@ -0,0 +1,15 @@
"""vLLM-Ascend framework strategy."""
from .registry import (
check_vllm_ascend_registry,
fetch_vllm_ascend_registry,
parse_vllm_ascend_registry,
)
from .strategy import VllmAscendStrategy
__all__ = [
"VllmAscendStrategy",
"fetch_vllm_ascend_registry",
"parse_vllm_ascend_registry",
"check_vllm_ascend_registry",
]
@@ -0,0 +1,104 @@
"""vLLM-Ascend __init__.py parsing for registered models.
vLLM-Ascend uses ModelRegistry.register_model() calls in __init__.py
to register model architectures, unlike vLLM which uses a dict-based registry.
"""
import os
import re
from typing import Optional
from ...context import Context
from ...http_utils import _get
_INIT_PATH = "vllm_ascend/models/__init__.py"
_BROWSER_UA = {
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
}
def fetch_vllm_ascend_registry(ctx: Context, ref: Optional[str] = None, verbose: bool = False) -> str:
"""Fetch vLLM-Ascend models/__init__.py source.
In local mode (ctx.is_local) the file is read from disk; otherwise
it is fetched from the GitHub raw CDN.
"""
if ctx.is_local:
p = os.path.join(ctx.local_dir, _INIT_PATH)
if verbose:
print(f" [registry] reading local {p}")
try:
with open(p, encoding="utf-8") as f:
body = f.read()
except OSError:
raise RuntimeError(f"Could not read local __init__.py at {p}")
if not body:
raise RuntimeError(f"Empty local __init__.py at {p}")
return body
ref = ref or ctx.branch
url = f"https://raw.githubusercontent.com/{ctx.repo}/{ref}/{_INIT_PATH}"
if verbose:
print(f" [registry] fetching {url}")
body, _ = _get(url, ua=_BROWSER_UA)
if not body:
raise RuntimeError(f"Could not fetch __init__.py from {url}")
return body
def parse_vllm_ascend_registry(source: str, verbose: bool = False):
"""Parse __init__.py source and extract registered models.
Looks for ModelRegistry.register_model() calls and extracts:
- arch_name: the architecture name (first argument)
- class_path: the module:class path (second argument)
Returns:
registered: dict[arch_name] = class_path
"""
# Match: ModelRegistry.register_model("ArchName", "module.path:ClassName")
# Handles multi-line calls with various whitespace
pattern = re.compile(
r'ModelRegistry\.register_model\s*\(\s*'
r'["\'](\w+)["\']\s*,\s*'
r'["\']([^"\']+)["\']',
re.MULTILINE
)
registered = {}
for m in pattern.finditer(source):
arch_name = m.group(1)
class_path = m.group(2)
registered[arch_name] = class_path
if verbose and registered:
print(f" [registry] found {len(registered)} registered models")
return registered
def check_vllm_ascend_registry(arch: str, registered: dict) -> dict:
"""Check an architecture against parsed vLLM-Ascend registry data.
Returns a dict with status details.
"""
if arch in registered:
class_path = registered[arch]
# Parse "module.path:ClassName" format
if ":" in class_path:
module_path, class_name = class_path.rsplit(":", 1)
else:
module_path = class_path
class_name = class_path.split(".")[-1]
return {
"status": "supported",
"module": module_path,
"class_name": class_name,
}
return {"status": "unsupported"}
@@ -0,0 +1,108 @@
"""vLLM-Ascend strategy: __init__.py parsing for registered models."""
import os
from typing import Optional, Tuple
from ...context import Context
from .registry import (
check_vllm_ascend_registry,
fetch_vllm_ascend_registry,
parse_vllm_ascend_registry,
)
from ..base import FrameworkStrategy
class VllmAscendStrategy(FrameworkStrategy):
name = "vllm-ascend"
label = "vLLM-Ascend"
repo = "vllm-project/vllm-ascend"
branch = "main"
models_dir = "vllm_ascend/models"
docs_url = "https://docs.vllm.ai/projects/ascend/en/latest/"
def __init__(self):
super().__init__()
self._registry_file_path = None
def check_support(
self, arch: str, ctx: Context, token: Optional[str] = None, verbose: bool = False
) -> Tuple[bool, Optional[str]]:
supported, file_path = super().check_support(arch, ctx, token, verbose)
if supported:
return True, file_path
try:
init_source = fetch_vllm_ascend_registry(ctx=ctx, ref=self.branch, verbose=verbose)
registered = parse_vllm_ascend_registry(init_source, verbose=verbose)
except RuntimeError as e:
if verbose:
print(f" [registry-fallback] skipped: {e}")
return False, None
if arch not in registered:
return False, None
class_path = registered[arch]
if ":" in class_path:
module_path = class_path.rsplit(":", 1)[0]
else:
module_path = class_path
if not module_path.startswith("vllm_ascend.models."):
return False, None
resolved = self._resolve_new_style_path(module_path, arch, ctx, verbose)
if resolved:
self._registry_file_path = resolved
if verbose:
print(f" [registry-fallback] found via registry: {resolved}")
return True, resolved
return False, None
def _resolve_new_style_path(self, module, arch, ctx, verbose):
pkg_rel = module.replace(".", os.sep)
candidates = [
f"{pkg_rel}/__init__.py",
f"{pkg_rel}.py",
]
for rel in candidates:
body = ctx.read_source(rel)
if body and arch in body:
return rel
if ctx.is_local:
pkg_dir = os.path.join(ctx.local_dir, pkg_rel)
if os.path.isdir(pkg_dir):
for fname in sorted(os.listdir(pkg_dir)):
if fname.endswith(".py") and fname != "__init__.py":
fpath = os.path.join(pkg_dir, fname)
try:
with open(fpath, encoding="utf-8") as f:
body = f.read()
except OSError:
continue
if arch in body:
return f"{pkg_rel}/{fname}"
return None
def extra_checks(self, arch: str, ctx: Context, token: Optional[str], verbose: bool, ref: Optional[str] = None):
"""Parse __init__.py for registered model info."""
try:
init_source = fetch_vllm_ascend_registry(ctx=ctx, ref=ref or self.branch, verbose=verbose)
registered = parse_vllm_ascend_registry(init_source, verbose=verbose)
print(f" Registry parsed: {len(registered)} architectures registered")
info = check_vllm_ascend_registry(arch, registered)
status = info["status"]
if status == "supported":
print(f" Module : {info['module']}")
print(f" Class : {info['class_name']}")
return info
except RuntimeError as e:
if verbose:
print(f" [registry] skipped: {e}")
return None
def version_path(self, file_path: Optional[str]) -> Optional[str]:
"""Prefer registry-resolved path, then file_path, then __init__.py."""
return self._registry_file_path or file_path or f"{self.models_dir}/__init__.py"
def format_summary_extra(self, info) -> None:
if not info:
return
status = info["status"]
if status == "supported":
print(f" Module: {info['module']}")
@@ -0,0 +1,41 @@
"""Minimal HTTP helpers (standard library only)."""
import json
import urllib.error
import urllib.request
UA = {"User-Agent": "model-support-check/1.0"}
BROWSER_UA = {
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
}
def _get(url, headers=None, timeout=25, ua=None):
h = dict(ua or UA)
if headers:
h.update(headers)
req = urllib.request.Request(url, headers=h)
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.read().decode("utf-8", "replace"), r.headers
except urllib.error.HTTPError as e:
if e.code in (403, 429):
raise RuntimeError(f"GitHub rate limit / forbidden ({e.code})")
if e.code in (401, 404):
return None, e.headers
raise
def _get_json(url, token=None):
headers = {"Authorization": f"Bearer {token}"} if token else {}
try:
body, _ = _get(url, headers)
except RuntimeError:
return None
if body is None:
return None
return json.loads(body)
@@ -0,0 +1,22 @@
"""State management for model-support-checker.
State lives in <skill root>/.state/ (gitignored via .gitignore) so the
first-run choice (GitHub PAT vs local clone) survives across runs without
being tracked by git. The GitHub token itself is NEVER stored here.
"""
from .doctor import doctor, print_report
from .persistence import load, save, reset
from .setup import first_run_menu, ensure_setup
from .refresh import start_refresh
__all__ = [
"load",
"save",
"reset",
"first_run_menu",
"ensure_setup",
"start_refresh",
"doctor",
"print_report",
]
@@ -0,0 +1,40 @@
"""Repository cloning and validation."""
import os
import subprocess
from ..framework_strategies import STRATEGIES
from .constants import REPOS_DIR
def clone(url, target):
"""Clone url into target (full history -- shallow clones break step 4)."""
if os.path.isdir(os.path.join(target, ".git")):
print(f" reusing existing clone: {target}")
return target
os.makedirs(os.path.dirname(target), exist_ok=True)
print(f" cloning {url} -> {target} (full history, may take a while)...")
r = subprocess.run(["git", "clone", url, target])
if r.returncode != 0:
raise RuntimeError(f"git clone failed for {url}")
return target
def validate_checkout(path, fw_name):
"""Validate that a path looks like a valid framework checkout."""
strategy = STRATEGIES[fw_name]
if not os.path.isfile(os.path.join(path, strategy.models_dir, "__init__.py")):
print(
f" WARNING: {strategy.models_dir} not found under {path}; "
f"is this really a {strategy.label} checkout?"
)
def get_clone_url(fw_name):
"""Get the clone URL for a framework."""
return f"https://github.com/{STRATEGIES[fw_name].repo}"
def get_default_clone_path(fw_name):
"""Get the default clone path for a framework."""
return os.path.join(REPOS_DIR, fw_name)
@@ -0,0 +1,8 @@
"""State constants."""
import os
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
STATE_DIR = os.path.join(ROOT, ".state")
REPOS_DIR = os.path.join(STATE_DIR, "repos")
STATE_FILE = os.path.join(STATE_DIR, "state.json")
@@ -0,0 +1,169 @@
"""Health checks for persisted setup state.
The `doctor()` function inspects `.state/state.json` and the configured
framework checkouts, returning a list of actionable issues. It is run
automatically on every invocation so callers (human or agent) do not need
to manually inspect `.state/` with `ls` or `cat`.
"""
import os
from ..framework_strategies import STRATEGIES
from .constants import STATE_FILE
class Issue:
"""A single doctor issue with severity and an actionable message."""
def __init__(self, severity, message, framework=None):
self.severity = severity # "error" | "warning"
self.message = message
self.framework = framework
def __str__(self):
prefix = "ERROR" if self.severity == "error" else "WARN"
if self.framework:
return f"[{prefix} {self.framework}] {self.message}"
return f"[{prefix}] {self.message}"
def doctor(st, args=None, framework=None):
"""Check setup state and local checkouts.
Args:
st: The loaded state dict, or None if no state file exists.
args: Optional argparse namespace with path overrides.
framework: Framework name to limit the check to, or None/'all' to check
every configured framework.
Returns:
A list of Issue objects. An empty list means the configuration looks
healthy enough to proceed.
"""
issues = []
if st is None:
issues.append(
Issue(
"error",
f"No setup state found ({STATE_FILE} missing). "
"Run: python3 main.py --setup local",
)
)
return issues
mode = st.get("mode")
if mode not in ("local", "token"):
issues.append(
Issue(
"error",
f"Invalid mode '{mode}' in state. Run: python3 main.py --setup local",
)
)
return issues
if mode == "token":
token = _get_token(args)
if not token:
issues.append(
Issue(
"warning",
"GitHub token mode but no GITHUB_TOKEN set. "
"Code search results will be best-effort only.",
)
)
return issues
# Local mode: validate the requested framework path(s).
frameworks = list(STRATEGIES) if framework in (None, "all") else [framework]
for fw_name in frameworks:
if fw_name not in STRATEGIES:
issues.append(Issue("error", f"Unknown framework: {fw_name}"))
continue
strategy = STRATEGIES[fw_name]
path_key = f"{fw_name.replace('-', '_')}_path"
configured_path = st.get(path_key)
override_path = _get_override_path(args, fw_name)
path = override_path or configured_path
if not configured_path and not override_path:
issues.append(
Issue(
"error",
f"No local checkout configured. Run: python3 main.py --setup local --{fw_name}-path /path/to/{fw_name}",
framework=strategy.label,
)
)
continue
if override_path and not configured_path:
issues.append(
Issue(
"warning",
f"Using one-time override {override_path}; state is missing a saved path.",
framework=strategy.label,
)
)
if not os.path.isdir(path):
issues.append(
Issue(
"error",
f"Configured path does not exist: {path}",
framework=strategy.label,
)
)
continue
if not os.path.isdir(os.path.join(path, ".git")):
issues.append(
Issue(
"error",
f"Path is not a git repository: {path}",
framework=strategy.label,
)
)
continue
init_file = os.path.join(path, strategy.models_dir, "__init__.py")
if not os.path.isfile(init_file):
issues.append(
Issue(
"warning",
f"{strategy.models_dir}/__init__.py not found under {path}; "
"is this really a checkout?",
framework=strategy.label,
)
)
return issues
def _get_token(args):
if args and getattr(args, "token", None):
return args.token
return os.environ.get("GITHUB_TOKEN")
def _get_override_path(args, fw_name):
if args is None:
return None
attr = f"{fw_name.replace('-', '_')}_path"
return getattr(args, attr, None)
def print_report(issues):
"""Print doctor issues in a consistent format.
Returns True if any errors were reported.
"""
has_errors = any(i.severity == "error" for i in issues)
if not issues:
print("doctor: setup state looks healthy.")
return False
print("=== doctor ===")
for issue in issues:
print(f" {issue}")
print()
return has_errors
@@ -0,0 +1,33 @@
"""State persistence: load, save, reset."""
import json
import os
from .constants import STATE_DIR, STATE_FILE, REPOS_DIR
def load():
"""Return the saved state dict, or None if not set up yet."""
try:
with open(STATE_FILE, encoding="utf-8") as f:
st = json.load(f)
return st if isinstance(st, dict) and st.get("mode") else None
except (OSError, ValueError):
return None
def save(st):
os.makedirs(STATE_DIR, exist_ok=True)
with open(STATE_FILE, "w", encoding="utf-8") as f:
json.dump(st, f, indent=2)
return st
def reset():
"""Forget setup state. Cloned repos under .state/repos/ are kept and will
be reused automatically by a later `--setup local`."""
try:
os.remove(STATE_FILE)
print(f"Removed {STATE_FILE} (cloned repos kept at {REPOS_DIR}).")
except FileNotFoundError:
print("No saved state found.")
@@ -0,0 +1,68 @@
"""Background checkout refresh."""
import subprocess
import threading
def fetch_status(path, result, key):
"""Background worker: `git fetch` a local checkout and count how far
behind its upstream it is. Writes into result[key]; NEVER raises — any
failure is recorded as {"error": ...} so the main flow is never blocked."""
info = {"path": path}
try:
remotes = subprocess.run(
["git", "-C", path, "remote"],
capture_output=True, text=True,
).stdout.strip()
if not remotes:
info["no_upstream"] = True
result[key] = info
return
f = subprocess.run(
["git", "-C", path, "fetch", "--quiet", "origin"],
capture_output=True, text=True, timeout=300,
)
if f.returncode != 0:
lines = [ln for ln in f.stderr.strip().splitlines() if ln.strip()]
info["error"] = lines[-1] if lines else "git fetch failed"
else:
up = subprocess.run(
["git", "-C", path, "rev-parse", "--abbrev-ref",
"--symbolic-full-name", "@{upstream}"],
capture_output=True, text=True,
)
head = subprocess.run(
["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"],
capture_output=True, text=True,
).stdout.strip()
if up.returncode != 0 or head in ("", "HEAD"):
info["no_upstream"] = True
else:
cnt = subprocess.run(
["git", "-C", path, "rev-list", "--count",
f"HEAD..{up.stdout.strip()}"],
capture_output=True, text=True,
)
if cnt.returncode == 0 and cnt.stdout.strip().isdigit():
info["behind"] = int(cnt.stdout.strip())
else:
info["no_upstream"] = True
except (subprocess.SubprocessError, OSError, ValueError) as e:
# ponytail: broad catch is the point — refresh must never be fatal
info["error"] = str(e)
result[key] = info
def start_refresh(framework_paths):
"""Spawn daemon threads to refresh local checkouts concurrently.
Returns (results_dict, threads); results are filled in as workers finish.
"""
results = {}
threads = []
for key, path in framework_paths.items():
t = threading.Thread(target=fetch_status, args=(path, results, key),
daemon=True)
t.start()
threads.append(t)
return results, threads
@@ -0,0 +1,95 @@
"""Interactive setup and first-run menu."""
import os
import sys
from ..framework_strategies import STRATEGIES
from .clone import clone, validate_checkout, get_clone_url, get_default_clone_path
from .constants import REPOS_DIR, STATE_FILE
from .persistence import save
def print_tradeoffs():
"""Print the tradeoffs between local clone and GitHub PAT modes."""
print(
"First-run setup: choose how to access vLLM/SGLang sources.\n"
"\n"
" 1) Local clone (recommended)\n"
" + definitive results: files grepped on disk, no rate limits\n"
" + fast repeat checks; version detection from full git history\n"
" + works offline after the initial clone\n"
" - one-time download (vLLM full history ~1 GB+, SGLang smaller)\n"
" - needs occasional 'git pull' or answers go stale\n"
"\n"
" 2) GitHub PAT\n"
" + no disk usage; always reflects remote main\n"
" - code search 403s without a token; anonymous results are\n"
" best-effort only\n"
" - API rate limits can break batch/version checks\n"
" - GITHUB_TOKEN must be provided every run (never stored here)"
)
def first_run_menu():
"""Interactive TTY menu. Returns the chosen mode ('local' | 'token')."""
print_tradeoffs()
while True:
choice = input("Choose [1/2] (default 1): ").strip()
if choice in ("", "1"):
return "local"
if choice == "2":
return "token"
print("Please enter 1 or 2.")
def ensure_setup(mode, args):
"""Create and persist setup state. Returns the new state dict."""
st = {"mode": mode}
if mode == "local":
vllm_path = args.vllm_path
sglang_path = args.sglang_path
vllm_ascend_path = getattr(args, "vllm_ascend_path", None)
if sys.stdin.isatty():
if not vllm_path:
raw = input(
f"vLLM checkout path [Enter = clone into {REPOS_DIR}/vllm]: "
).strip()
vllm_path = raw or None
if not sglang_path:
raw = input(
f"SGLang checkout path [Enter = clone into {REPOS_DIR}/sglang]: "
).strip()
sglang_path = raw or None
if not vllm_ascend_path:
raw = input(
f"vLLM-Ascend checkout path [Enter = clone into {REPOS_DIR}/vllm-ascend]: "
).strip()
vllm_ascend_path = raw or None
if not vllm_path:
vllm_path = clone(get_clone_url("vllm"), get_default_clone_path("vllm"))
if not sglang_path:
sglang_path = clone(get_clone_url("sglang"), get_default_clone_path("sglang"))
if not vllm_ascend_path:
vllm_ascend_path = clone(get_clone_url("vllm-ascend"), get_default_clone_path("vllm-ascend"))
st["vllm_path"] = os.path.abspath(os.path.expanduser(vllm_path))
st["sglang_path"] = os.path.abspath(os.path.expanduser(sglang_path))
st["vllm_ascend_path"] = os.path.abspath(os.path.expanduser(vllm_ascend_path))
validate_checkout(st["vllm_path"], "vllm")
validate_checkout(st["sglang_path"], "sglang")
validate_checkout(st["vllm_ascend_path"], "vllm-ascend")
elif mode == "token":
if not args.token and not os.environ.get("GITHUB_TOKEN"):
print(
"NOTE: no GITHUB_TOKEN set. Code search needs it; export "
"GITHUB_TOKEN per run (it is never stored in .state/)."
)
else:
raise RuntimeError(f"Unknown setup mode: {mode}")
save(st)
print(f"Setup saved ({mode}) -> {STATE_FILE}")
return st