feat: add queue-aware adaptive scheduling

This commit is contained in:
CoolBoy
2026-08-04 20:22:08 +08:00
parent ccae7ff8f3
commit 882479e43e
12 changed files with 1586 additions and 95 deletions

View File

@@ -44,18 +44,50 @@ Optional tuning:
- `MODELHUB_AGENT_GPUS` - `MODELHUB_AGENT_GPUS`
- `MODELHUB_AGENT_EXTRA_ARGS` - `MODELHUB_AGENT_EXTRA_ARGS`
- `MODELHUB_GPU_STRATEGY_STATE_PATH` default `.modelhub_state/gpu_strategy.json` - `MODELHUB_GPU_STRATEGY_STATE_PATH` default `.modelhub_state/gpu_strategy.json`
- `MODELHUB_MARKET_INTELLIGENCE_PATH` default `.modelhub_state/market_intelligence.json`
- `MODELHUB_MARKET_QUEUE_REFRESH_SECONDS` default `600`
- `MODELHUB_MARKET_FRAMEWORK_REFRESH_SECONDS` default `21600`
- `MODELHUB_MARKET_THROUGHPUT_WINDOW_HOURS` default `6`
- `MODELHUB_MARKET_FRAMEWORK_MIN_SAMPLES` default `100`
- `MODELSCOPE_PAGE_INTERVAL_SECONDS` default `0.25` - `MODELSCOPE_PAGE_INTERVAL_SECONDS` default `0.25`
- `MODELSCOPE_PAGE_CACHE_TTL_SECONDS` default `900` - `MODELSCOPE_PAGE_CACHE_TTL_SECONDS` default `900`
- `MODELHUB_AGENT_VERIFY_CACHE_TTL_SECONDS` default `900` - `MODELHUB_AGENT_VERIFY_CACHE_TTL_SECONDS` default `900`
## Adaptive GPU Strategy ## Adaptive GPU Strategy
When no explicit GPU override is supplied, the worker uses a local 50/30/20 When no explicit GPU override is supplied, the worker uses a queue-aware 50/30/20
strategy generation: strategy generation:
- 50%: the three long-term GPUs with the best Wilson lower confidence score and at least 100 terminal samples - 50%: the three long-term GPUs with the best Wilson lower confidence score and at least 100 terminal samples
- 30%: round-robin exploration across every currently supported GPU - 30%: exploration across every currently supported GPU
- 20%: the best GPU among the latest 1,000 terminal tasks - 20%: the top recent GPUs among the latest 1,000 terminal tasks
The 50/30/20 category ratio remains exact across accepted tasks. Inside each
category, weighted fair scheduling combines the category's historical rank with
live public market data:
- estimated backlog hours (`waiting / recent completions per hour`) instead of raw queue length
- recent public success quality, scored with a Wilson lower confidence bound
- machine availability, running workers, and advertised concurrency
- a circuit breaker for unavailable or apparently stalled GPU pools
This optimizes expected successful completions rather than blindly selecting the
smallest queue. Queue/throughput data is refreshed every 10 minutes and persisted
in `.modelhub_state/market_intelligence.json`. A failed refresh keeps the last good
snapshot, uses a retry backoff, and never blocks normal submissions.
For each compatible model/GPU pair, the worker also ranks the GPU's supported
frameworks using ModelHub's public aggregate `modelCount` and `successCount` data,
then blends in the worker's own GPU+framework outcomes with a capped weight.
Only frameworks with at least 100 samples receive statistical priority; the
legacy safe framework order remains the fallback. Framework statistics refresh
every 6 hours, so they do not add per-model API traffic. Newly published
frameworks are discovered automatically, but receive no novelty bonus: they can
win only when their confidence-adjusted success score is better. A new framework
is eligible only after the authenticated official build-config endpoint returns
a complete config that passes local structure, placeholder, framework-name, and
GPU-parallelism validation. Valid official configs are cached and refreshed with
the framework snapshot; local templates remain the fail-safe fallback.
Only platform-accepted tasks count. After exactly 200 accepted tasks, the next Only platform-accepted tasks count. After exactly 200 accepted tasks, the next
poll cycle reloads all account history, generates a new immutable strategy snapshot, poll cycle reloads all account history, generates a new immutable strategy snapshot,
@@ -104,12 +136,15 @@ health response expose `agent_version`; version `2026.08.02.3` or newer includes
duplicate replacement behavior, while version `2026.08.02.4` adds adaptive duplicate replacement behavior, while version `2026.08.02.4` adds adaptive
candidate-window expansion and skip-reason reporting. Version `2026.08.02.5` candidate-window expansion and skip-reason reporting. Version `2026.08.02.5`
adds fail-closed model/GPU prechecks and persistent uniqueness exclusions. adds fail-closed model/GPU prechecks and persistent uniqueness exclusions.
Version `2026.08.04.1` adds queue/throughput intelligence, GPU health circuit
breaking, weighted-fair scheduling, live framework/config discovery, and
confidence-ranked public-plus-local framework selection.
## Deploy ## Deploy
Create a tag and submit the repository URL plus tag in "我的适配智能体". Create a tag and submit the repository URL plus tag in "我的适配智能体".
```bash ```bash
git tag agent-v6 git tag agent-v12
git push origin agent-v6 git push origin agent-v12
``` ```

View File

@@ -9,6 +9,7 @@ It currently supports:
- multiple ModelHub tokens read from `KEY.md` and `KEYS.md` - multiple ModelHub tokens read from `KEY.md` and `KEYS.md`
- automatic task/framework/template selection across the supported GPU catalog - automatic task/framework/template selection across the supported GPU catalog
- adaptive long-term/exploration/recent GPU scheduling with a persistent local snapshot - adaptive long-term/exploration/recent GPU scheduling with a persistent local snapshot
- live queue/throughput-aware GPU weighting and confidence-ranked framework selection
## Layout ## Layout
@@ -73,8 +74,19 @@ bash run_poll.sh --dry-run
## Behavior ## Behavior
- The runner auto-discovers all safe GPU/template combinations from the public submit catalog. - The runner auto-discovers all safe GPU/template combinations from the public submit catalog.
- Automatic GPU selection uses smooth 50/30/20 scheduling: long-term Wilson-ranked top 3 GPUs, - Automatic GPU selection uses exact 50/30/20 accepted-task scheduling: long-term
all supported GPUs, and the best GPU from the latest 1,000 terminal tasks. Wilson-ranked top 3 GPUs, all supported GPUs, and the top GPUs from the latest
1,000 terminal tasks.
- Within each category, weighted-fair scheduling uses estimated queue backlog hours,
recent public throughput/success, machine availability, and worker concurrency.
Unavailable or stalled GPU pools are circuit-broken instead of continuing to absorb work.
- Compatible frameworks are ranked by ModelHub public aggregate success statistics
plus capped local GPU+framework evidence, with a 100-sample public minimum and
Wilson confidence bounds. The legacy safe order is retained whenever evidence
is missing or too small.
- New frameworks are discovered from the live catalog but get no novelty bonus.
They are eligible only with a complete official build config that passes local
validation; cached local templates remain the fallback if live config sync fails.
- A strategy generation lasts exactly 200 platform-accepted submissions. Rejected API calls and - A strategy generation lasts exactly 200 platform-accepted submissions. Rejected API calls and
duplicates do not advance it. The next cycle refreshes platform history before submitting again. duplicates do not advance it. The next cycle refreshes platform history before submitting again.
- Strategy state is stored in `.modelhub_state/gpu_strategy.json`; a generation never recalculates - Strategy state is stored in `.modelhub_state/gpu_strategy.json`; a generation never recalculates
@@ -116,6 +128,7 @@ Common flags:
- `--dry-run`: plan only, do not submit - `--dry-run`: plan only, do not submit
- `--gpu-strategy-refresh-submissions`: accepted tasks per strategy generation (default `200`) - `--gpu-strategy-refresh-submissions`: accepted tasks per strategy generation (default `200`)
- `--disable-gpu-strategy`: restore legacy ordering; explicit `--gpu/--gpus` also bypasses adaptive selection - `--disable-gpu-strategy`: restore legacy ordering; explicit `--gpu/--gpus` also bypasses adaptive selection
- `--disable-market-intelligence`: disable live queue/throughput and framework-stat weighting
- `run_daily.sh` injects `--daily-target 3` when no daily-target flag is provided. Set `SUBMIT_DAILY_TARGET` or pass `--daily-target` explicitly for a different target. - `run_daily.sh` injects `--daily-target 3` when no daily-target flag is provided. Set `SUBMIT_DAILY_TARGET` or pass `--daily-target` explicitly for a different target.
`run_poll.sh` adds: `run_poll.sh` adds:
@@ -151,6 +164,7 @@ Each run typically includes:
Persistent local scheduler state is written under `.modelhub_state/`: Persistent local scheduler state is written under `.modelhub_state/`:
- `gpu_strategy.json`: GPU ranks, generation progress, and 50/30/20 accepted counters - `gpu_strategy.json`: GPU ranks, generation progress, and 50/30/20 accepted counters
- `market_intelligence.json`: cached public queue, throughput, health, and framework statistics
- `account_capacity.json`: learned per-account active-task limits - `account_capacity.json`: learned per-account active-task limits
- `submission_exclusions.jsonl`: non-retryable model/GPU uniqueness rejections - `submission_exclusions.jsonl`: non-retryable model/GPU uniqueness rejections

View File

@@ -10,6 +10,14 @@ from common import utc_now, write_json
from gpu_strategy import DEFAULT_GPU_STRATEGY_PATH from gpu_strategy import DEFAULT_GPU_STRATEGY_PATH
from hf_discovery import HuggingFaceDiscovery from hf_discovery import HuggingFaceDiscovery
from main import DEFAULT_LEDGER_PATH, DEFAULT_RUNS_DIR, make_run_dir, run_submission from main import DEFAULT_LEDGER_PATH, DEFAULT_RUNS_DIR, make_run_dir, run_submission
from market_intelligence import (
DEFAULT_FETCH_WORKERS,
DEFAULT_FRAMEWORK_MIN_SAMPLES,
DEFAULT_FRAMEWORK_REFRESH_SECONDS,
DEFAULT_MARKET_INTELLIGENCE_PATH,
DEFAULT_QUEUE_REFRESH_SECONDS,
DEFAULT_THROUGHPUT_WINDOW_HOURS,
)
from modelhub_client import DEFAULT_CAPACITY_STATE_PATH, ModelHubClient, ModelHubClientPool from modelhub_client import DEFAULT_CAPACITY_STATE_PATH, ModelHubClient, ModelHubClientPool
from outcome_tracker import OutcomeTracker from outcome_tracker import OutcomeTracker
from runner_common import DEFAULT_KEY_PATH, ensure_tokens from runner_common import DEFAULT_KEY_PATH, ensure_tokens
@@ -81,6 +89,11 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument("--skip-history-archive", action="store_true", help="Skip historical task archive download for this run") parser.add_argument("--skip-history-archive", action="store_true", help="Skip historical task archive download for this run")
parser.add_argument("--dry-run", action="store_true", help="Plan the day without creating tasks") parser.add_argument("--dry-run", action="store_true", help="Plan the day without creating tasks")
parser.add_argument("--disable-gpu-strategy", action="store_true", help="Disable adaptive 50/30/20 GPU scheduling") parser.add_argument("--disable-gpu-strategy", action="store_true", help="Disable adaptive 50/30/20 GPU scheduling")
parser.add_argument(
"--disable-market-intelligence",
action="store_true",
help="Disable live queue/throughput and public framework statistics",
)
parser.add_argument( parser.add_argument(
"--gpu-strategy-refresh-submissions", "--gpu-strategy-refresh-submissions",
type=int, type=int,
@@ -109,6 +122,41 @@ def build_parser() -> argparse.ArgumentParser:
) )
parser.add_argument("--gpu-strategy-recent-window", type=int, default=1000, help=argparse.SUPPRESS) parser.add_argument("--gpu-strategy-recent-window", type=int, default=1000, help=argparse.SUPPRESS)
parser.add_argument("--gpu-strategy-min-long-samples", type=int, default=100, help=argparse.SUPPRESS) parser.add_argument("--gpu-strategy-min-long-samples", type=int, default=100, help=argparse.SUPPRESS)
parser.add_argument(
"--market-intelligence-state-path",
default=os.getenv("MODELHUB_MARKET_INTELLIGENCE_PATH", str(DEFAULT_MARKET_INTELLIGENCE_PATH)),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-queue-refresh-seconds",
type=int,
default=int(os.getenv("MODELHUB_MARKET_QUEUE_REFRESH_SECONDS", str(DEFAULT_QUEUE_REFRESH_SECONDS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-framework-refresh-seconds",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FRAMEWORK_REFRESH_SECONDS", str(DEFAULT_FRAMEWORK_REFRESH_SECONDS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-throughput-window-hours",
type=int,
default=int(os.getenv("MODELHUB_MARKET_THROUGHPUT_WINDOW_HOURS", str(DEFAULT_THROUGHPUT_WINDOW_HOURS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-fetch-workers",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FETCH_WORKERS", str(DEFAULT_FETCH_WORKERS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-framework-min-samples",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FRAMEWORK_MIN_SAMPLES", str(DEFAULT_FRAMEWORK_MIN_SAMPLES))),
help=argparse.SUPPRESS,
)
parser.add_argument( parser.add_argument(
"--capacity-state-path", "--capacity-state-path",
default=os.getenv("MODELHUB_CAPACITY_STATE_PATH", str(DEFAULT_CAPACITY_STATE_PATH)), default=os.getenv("MODELHUB_CAPACITY_STATE_PATH", str(DEFAULT_CAPACITY_STATE_PATH)),
@@ -150,10 +198,37 @@ def make_wave_namespace(base_args: argparse.Namespace, wave: WaveSpec) -> argpar
skip_outcome_sync=getattr(base_args, "skip_outcome_sync", False), skip_outcome_sync=getattr(base_args, "skip_outcome_sync", False),
skip_history_archive=getattr(base_args, "skip_history_archive", False), skip_history_archive=getattr(base_args, "skip_history_archive", False),
disable_gpu_strategy=getattr(base_args, "disable_gpu_strategy", False), disable_gpu_strategy=getattr(base_args, "disable_gpu_strategy", False),
disable_market_intelligence=getattr(base_args, "disable_market_intelligence", False),
gpu_strategy_refresh_submissions=getattr(base_args, "gpu_strategy_refresh_submissions", 200), gpu_strategy_refresh_submissions=getattr(base_args, "gpu_strategy_refresh_submissions", 200),
gpu_strategy_state_path=getattr(base_args, "gpu_strategy_state_path", str(DEFAULT_GPU_STRATEGY_PATH)), gpu_strategy_state_path=getattr(base_args, "gpu_strategy_state_path", str(DEFAULT_GPU_STRATEGY_PATH)),
gpu_strategy_recent_window=getattr(base_args, "gpu_strategy_recent_window", 1000), gpu_strategy_recent_window=getattr(base_args, "gpu_strategy_recent_window", 1000),
gpu_strategy_min_long_samples=getattr(base_args, "gpu_strategy_min_long_samples", 100), gpu_strategy_min_long_samples=getattr(base_args, "gpu_strategy_min_long_samples", 100),
market_intelligence_state_path=getattr(
base_args,
"market_intelligence_state_path",
str(DEFAULT_MARKET_INTELLIGENCE_PATH),
),
market_queue_refresh_seconds=getattr(
base_args,
"market_queue_refresh_seconds",
DEFAULT_QUEUE_REFRESH_SECONDS,
),
market_framework_refresh_seconds=getattr(
base_args,
"market_framework_refresh_seconds",
DEFAULT_FRAMEWORK_REFRESH_SECONDS,
),
market_throughput_window_hours=getattr(
base_args,
"market_throughput_window_hours",
DEFAULT_THROUGHPUT_WINDOW_HOURS,
),
market_fetch_workers=getattr(base_args, "market_fetch_workers", DEFAULT_FETCH_WORKERS),
market_framework_min_samples=getattr(
base_args,
"market_framework_min_samples",
DEFAULT_FRAMEWORK_MIN_SAMPLES,
),
capacity_state_path=getattr(base_args, "capacity_state_path", str(DEFAULT_CAPACITY_STATE_PATH)), capacity_state_path=getattr(base_args, "capacity_state_path", str(DEFAULT_CAPACITY_STATE_PATH)),
capacity_probe_interval_cycles=getattr(base_args, "capacity_probe_interval_cycles", 3), capacity_probe_interval_cycles=getattr(base_args, "capacity_probe_interval_cycles", 3),
submit_concurrency=getattr(base_args, "submit_concurrency", 1), submit_concurrency=getattr(base_args, "submit_concurrency", 1),

View File

@@ -17,7 +17,7 @@ DEFAULT_GPU_STRATEGY_PATH = Path(".modelhub_state/gpu_strategy.json")
DEFAULT_REFRESH_SUBMISSIONS = 200 DEFAULT_REFRESH_SUBMISSIONS = 200
DEFAULT_RECENT_TERMINAL_WINDOW = 1000 DEFAULT_RECENT_TERMINAL_WINDOW = 1000
DEFAULT_LONG_TERM_MIN_SAMPLES = 100 DEFAULT_LONG_TERM_MIN_SAMPLES = 100
STRATEGY_STATE_VERSION = 1 STRATEGY_STATE_VERSION = 2
LONG_TERM = "long_term" LONG_TERM = "long_term"
ALL_SUPPORTED = "all_supported" ALL_SUPPORTED = "all_supported"
@@ -25,14 +25,17 @@ RECENT = "recent"
CATEGORIES = (LONG_TERM, ALL_SUPPORTED, RECENT) CATEGORIES = (LONG_TERM, ALL_SUPPORTED, RECENT)
CATEGORY_WEIGHTS = {LONG_TERM: 5, ALL_SUPPORTED: 3, RECENT: 2} CATEGORY_WEIGHTS = {LONG_TERM: 5, ALL_SUPPORTED: 3, RECENT: 2}
# The long-term half is split 50/30/20 across its three ranked GPUs.
LONG_TERM_GPU_PATTERN = (0, 1, 0, 2, 0, 1, 0, 1, 0, 2)
def _empty_category_counts() -> dict[str, int]: def _empty_category_counts() -> dict[str, int]:
return {category: 0 for category in CATEGORIES} return {category: 0 for category in CATEGORIES}
def _empty_gpu_category_counts(supported_gpus: list[str]) -> dict[str, dict[str, int]]:
return {
category: {gpu: 0 for gpu in supported_gpus}
for category in CATEGORIES
}
def _gpu_name(record: dict[str, Any]) -> str: def _gpu_name(record: dict[str, Any]) -> str:
return str(record.get("gpuType") or record.get("targetGpu") or "").strip() return str(record.get("gpuType") or record.get("targetGpu") or "").strip()
@@ -160,12 +163,11 @@ def build_strategy_snapshot(
recent = _summarize_gpu_records(recent_tasks, supported_gpus) recent = _summarize_gpu_records(recent_tasks, supported_gpus)
recent_ranked = _rank_gpu_summaries(recent) recent_ranked = _rank_gpu_summaries(recent)
recent_qualified = [item for item in recent_ranked if int(item["terminal"]) >= 20] recent_qualified = [item for item in recent_ranked if int(item["terminal"]) >= 20]
if recent_qualified: recent_source = recent_qualified if recent_qualified else (recent_ranked if recent_tasks else [])
recent_gpu = str(recent_qualified[0]["gpu"]) recent_gpus = [str(item["gpu"]) for item in recent_source[: min(3, len(supported_gpus))]]
elif recent_tasks and recent_ranked: if not recent_gpus:
recent_gpu = str(recent_ranked[0]["gpu"]) recent_gpus = list(long_term_gpus[: min(3, len(supported_gpus))])
else: recent_gpu = recent_gpus[0]
recent_gpu = long_term_gpus[0]
return { return {
"version": STRATEGY_STATE_VERSION, "version": STRATEGY_STATE_VERSION,
@@ -176,6 +178,7 @@ def build_strategy_snapshot(
"supportedGpus": supported_gpus, "supportedGpus": supported_gpus,
"longTermGpus": long_term_gpus, "longTermGpus": long_term_gpus,
"recentGpu": recent_gpu, "recentGpu": recent_gpu,
"recentGpus": recent_gpus,
"recentTerminalCount": len(recent_tasks), "recentTerminalCount": len(recent_tasks),
"refreshSubmissions": max(1, int(refresh_submissions)), "refreshSubmissions": max(1, int(refresh_submissions)),
"recentTerminalWindow": max(1, int(recent_terminal_window)), "recentTerminalWindow": max(1, int(recent_terminal_window)),
@@ -183,6 +186,7 @@ def build_strategy_snapshot(
"acceptedSinceRefresh": 0, "acceptedSinceRefresh": 0,
"acceptedTotal": 0, "acceptedTotal": 0,
"acceptedByCategory": _empty_category_counts(), "acceptedByCategory": _empty_category_counts(),
"acceptedByGpuCategory": _empty_gpu_category_counts(supported_gpus),
"longTermStats": all_time_ranked, "longTermStats": all_time_ranked,
"recentStats": recent_ranked, "recentStats": recent_ranked,
"lastRefreshError": None, "lastRefreshError": None,
@@ -208,15 +212,20 @@ class GPUStrategyManager:
refresh_submissions: int = DEFAULT_REFRESH_SUBMISSIONS, refresh_submissions: int = DEFAULT_REFRESH_SUBMISSIONS,
recent_terminal_window: int = DEFAULT_RECENT_TERMINAL_WINDOW, recent_terminal_window: int = DEFAULT_RECENT_TERMINAL_WINDOW,
long_term_min_samples: int = DEFAULT_LONG_TERM_MIN_SAMPLES, long_term_min_samples: int = DEFAULT_LONG_TERM_MIN_SAMPLES,
market_intelligence: Any | None = None,
log_fn: Callable[[str], None] | None = None, log_fn: Callable[[str], None] | None = None,
) -> None: ) -> None:
self.path = Path(path) self.path = Path(path)
self.refresh_submissions = max(1, int(refresh_submissions)) self.refresh_submissions = max(1, int(refresh_submissions))
self.recent_terminal_window = max(1, int(recent_terminal_window)) self.recent_terminal_window = max(1, int(recent_terminal_window))
self.long_term_min_samples = max(1, int(long_term_min_samples)) self.long_term_min_samples = max(1, int(long_term_min_samples))
self.market_intelligence = market_intelligence
self.log = log_fn or (lambda message: print(message, flush=True)) self.log = log_fn or (lambda message: print(message, flush=True))
self.state: dict[str, Any] | None = None self.state: dict[str, Any] | None = None
def set_market_intelligence(self, market_intelligence: Any | None) -> None:
self.market_intelligence = market_intelligence
def _load(self) -> dict[str, Any] | None: def _load(self) -> dict[str, Any] | None:
try: try:
value = read_json(self.path) value = read_json(self.path)
@@ -357,22 +366,49 @@ class GPUStrategyManager:
accepted = max(0, int(self.state.get("acceptedSinceRefresh") or 0)) accepted = max(0, int(self.state.get("acceptedSinceRefresh") or 0))
return max(0, refresh_every - accepted) return max(0, refresh_every - accepted)
def _category_gpu_order(self, category: str, occurrence: int) -> list[str]: def _category_gpu_order(self, category: str, gpu_counts: dict[str, int]) -> list[str]:
assert self.state is not None assert self.state is not None
supported = list(self.state.get("supportedGpus") or []) supported = list(self.state.get("supportedGpus") or [])
long_term = [gpu for gpu in self.state.get("longTermGpus") or [] if gpu in supported] long_term = [gpu for gpu in self.state.get("longTermGpus") or [] if gpu in supported]
recent_gpu = str(self.state.get("recentGpu") or "") recent = [gpu for gpu in self.state.get("recentGpus") or [] if gpu in supported]
if not recent:
recent_gpu = str(self.state.get("recentGpu") or "")
if recent_gpu in supported:
recent = [recent_gpu]
if category == LONG_TERM and long_term: if category == LONG_TERM:
desired_index = LONG_TERM_GPU_PATTERN[occurrence % len(LONG_TERM_GPU_PATTERN)] eligible = long_term
desired = long_term[min(desired_index, len(long_term) - 1)] base_pattern = (5.0, 3.0, 2.0)
return [desired, *(gpu for gpu in long_term if gpu != desired)] elif category == RECENT:
if category == RECENT and recent_gpu in supported: eligible = recent
return [recent_gpu] base_pattern = (6.0, 3.0, 1.0)
if supported: else:
offset = occurrence % len(supported) eligible = supported
return [*supported[offset:], *supported[:offset]] base_pattern = tuple(1.0 for _ in eligible)
return [] if not eligible:
return []
weights: dict[str, float] = {}
for index, gpu in enumerate(eligible):
base = base_pattern[min(index, len(base_pattern) - 1)]
market_weight = 1.0
if self.market_intelligence is not None:
try:
market_weight = float(self.market_intelligence.gpu_weight(gpu, category=category))
except Exception:
market_weight = 1.0
weights[gpu] = max(0.05, base * market_weight)
total_weight = sum(weights.values()) or 1.0
next_total = sum(max(0, int(gpu_counts.get(gpu, 0))) for gpu in eligible) + 1
rank_index = {gpu: index for index, gpu in enumerate(eligible)}
return sorted(
eligible,
key=lambda gpu: (
-((weights[gpu] / total_weight) * next_total - max(0, int(gpu_counts.get(gpu, 0)))),
rank_index[gpu],
),
)
def order_candidates(self, candidates: list[dict[str, Any]]) -> list[dict[str, Any]]: def order_candidates(self, candidates: list[dict[str, Any]]) -> list[dict[str, Any]]:
if self.state is None or not candidates: if self.state is None or not candidates:
@@ -388,6 +424,14 @@ class GPUStrategyManager:
category: max(0, int((self.state.get("acceptedByCategory") or {}).get(category, 0))) category: max(0, int((self.state.get("acceptedByCategory") or {}).get(category, 0)))
for category in CATEGORIES for category in CATEGORIES
} }
stored_gpu_counts = self.state.get("acceptedByGpuCategory") or {}
virtual_gpu_counts = {
category: {
gpu: max(0, int((stored_gpu_counts.get(category) or {}).get(gpu, 0)))
for gpu in self.state.get("supportedGpus") or []
}
for category in CATEGORIES
}
def take_from_gpu(gpu: str) -> dict[str, Any] | None: def take_from_gpu(gpu: str) -> dict[str, Any] | None:
pool = by_gpu.get(gpu) or [] pool = by_gpu.get(gpu) or []
@@ -407,8 +451,7 @@ class GPUStrategyManager:
actual_category = planned_category actual_category = planned_category
categories_to_try = [planned_category, *(category for category in CATEGORIES if category != planned_category)] categories_to_try = [planned_category, *(category for category in CATEGORIES if category != planned_category)]
for category in categories_to_try: for category in categories_to_try:
occurrence = virtual_counts[category] for gpu in self._category_gpu_order(category, virtual_gpu_counts[category]):
for gpu in self._category_gpu_order(category, occurrence):
selected = take_from_gpu(gpu) selected = take_from_gpu(gpu)
if selected is not None: if selected is not None:
actual_category = category actual_category = category
@@ -429,8 +472,18 @@ class GPUStrategyManager:
annotated["strategyCategory"] = actual_category annotated["strategyCategory"] = actual_category
annotated["strategyPlannedCategory"] = planned_category annotated["strategyPlannedCategory"] = planned_category
annotated["strategyGeneration"] = int(self.state.get("generation") or 0) annotated["strategyGeneration"] = int(self.state.get("generation") or 0)
if self.market_intelligence is not None:
try:
annotated.update(self.market_intelligence.gpu_metadata(str(annotated.get("targetGpu") or "")))
except Exception:
pass
ordered.append(annotated) ordered.append(annotated)
virtual_counts[actual_category] += 1 virtual_counts[actual_category] += 1
selected_gpu = str(annotated.get("targetGpu") or "")
if selected_gpu:
virtual_gpu_counts[actual_category][selected_gpu] = (
virtual_gpu_counts[actual_category].get(selected_gpu, 0) + 1
)
return ordered return ordered
@@ -445,16 +498,29 @@ class GPUStrategyManager:
category: max(0, int((state.get("acceptedByCategory") or {}).get(category, 0))) category: max(0, int((state.get("acceptedByCategory") or {}).get(category, 0)))
for category in CATEGORIES for category in CATEGORIES
} }
supported = list(state.get("supportedGpus") or [])
stored_gpu_counts = state.get("acceptedByGpuCategory") or {}
gpu_category_counts = {
category: {
gpu: max(0, int((stored_gpu_counts.get(category) or {}).get(gpu, 0)))
for gpu in supported
}
for category in CATEGORIES
}
for candidate in candidates: for candidate in candidates:
category = str(candidate.get("strategyCategory") or ALL_SUPPORTED) category = str(candidate.get("strategyCategory") or ALL_SUPPORTED)
if category not in category_counts: if category not in category_counts:
category = ALL_SUPPORTED category = ALL_SUPPORTED
category_counts[category] += 1 category_counts[category] += 1
gpu = str(candidate.get("targetGpu") or "")
if gpu in gpu_category_counts[category]:
gpu_category_counts[category][gpu] += 1
accepted_count = len(candidates) accepted_count = len(candidates)
state["acceptedSinceRefresh"] = int(state.get("acceptedSinceRefresh") or 0) + accepted_count state["acceptedSinceRefresh"] = int(state.get("acceptedSinceRefresh") or 0) + accepted_count
state["acceptedTotal"] = int(state.get("acceptedTotal") or 0) + accepted_count state["acceptedTotal"] = int(state.get("acceptedTotal") or 0) + accepted_count
state["acceptedByCategory"] = category_counts state["acceptedByCategory"] = category_counts
state["acceptedByGpuCategory"] = gpu_category_counts
self.state = state self.state = state
write_json(self.path, state) write_json(self.path, state)
self._log_state("progress") self._log_state("progress")
@@ -473,8 +539,10 @@ class GPUStrategyManager:
"acceptedSinceRefresh": int(self.state.get("acceptedSinceRefresh") or 0), "acceptedSinceRefresh": int(self.state.get("acceptedSinceRefresh") or 0),
"refreshSubmissions": int(self.state.get("refreshSubmissions") or self.refresh_submissions), "refreshSubmissions": int(self.state.get("refreshSubmissions") or self.refresh_submissions),
"acceptedByCategory": dict(self.state.get("acceptedByCategory") or {}), "acceptedByCategory": dict(self.state.get("acceptedByCategory") or {}),
"acceptedByGpuCategory": dict(self.state.get("acceptedByGpuCategory") or {}),
"longTermGpus": list(self.state.get("longTermGpus") or []), "longTermGpus": list(self.state.get("longTermGpus") or []),
"recentGpu": self.state.get("recentGpu"), "recentGpu": self.state.get("recentGpu"),
"recentGpus": list(self.state.get("recentGpus") or []),
"recentTerminalCount": int(self.state.get("recentTerminalCount") or 0), "recentTerminalCount": int(self.state.get("recentTerminalCount") or 0),
"refreshDue": self.submissions_until_refresh <= 0, "refreshDue": self.submissions_until_refresh <= 0,
} }

View File

@@ -18,6 +18,15 @@ from history_stats import (
load_ledger, load_ledger,
update_history_archive, update_history_archive,
) )
from market_intelligence import (
DEFAULT_FETCH_WORKERS,
DEFAULT_FRAMEWORK_MIN_SAMPLES,
DEFAULT_FRAMEWORK_REFRESH_SECONDS,
DEFAULT_MARKET_INTELLIGENCE_PATH,
DEFAULT_QUEUE_REFRESH_SECONDS,
DEFAULT_THROUGHPUT_WINDOW_HOURS,
MarketIntelligenceManager,
)
from modelhub_client import ( from modelhub_client import (
DEFAULT_CAPACITY_STATE_PATH, DEFAULT_CAPACITY_STATE_PATH,
ModelHubAPIError, ModelHubAPIError,
@@ -30,7 +39,7 @@ from models import CandidateModel, HFModelSummary, ModelInspection
from outcome_tracker import DEFAULT_OUTCOMES_PATH, OutcomeTracker from outcome_tracker import DEFAULT_OUTCOMES_PATH, OutcomeTracker
from submission_claims import DEFAULT_CLAIMS_PATH, SubmissionClaimStore, candidate_key, diversify_candidates from submission_claims import DEFAULT_CLAIMS_PATH, SubmissionClaimStore, candidate_key, diversify_candidates
from submission_exclusions import DEFAULT_SUBMISSION_EXCLUSIONS_PATH, SubmissionExclusionStore from submission_exclusions import DEFAULT_SUBMISSION_EXCLUSIONS_PATH, SubmissionExclusionStore
from task_registry import TASK_SPEC_BY_TYPE, all_task_types, choose_framework_for_task, choose_text_generation_framework, pipeline_tags_for_task_types, task_specs_for_model from task_registry import TASK_SPEC_BY_TYPE, all_task_types, compatible_frameworks_for_task, pipeline_tags_for_task_types, task_specs_for_model
from template_selector import TemplateSelector from template_selector import TemplateSelector
@@ -61,6 +70,11 @@ def build_parser() -> argparse.ArgumentParser:
action="store_true", action="store_true",
help="Disable adaptive 50/30/20 GPU scheduling and keep the legacy candidate order", help="Disable adaptive 50/30/20 GPU scheduling and keep the legacy candidate order",
) )
parser.add_argument(
"--disable-market-intelligence",
action="store_true",
help="Disable live queue/throughput and public framework statistics",
)
parser.add_argument( parser.add_argument(
"--gpu-strategy-refresh-submissions", "--gpu-strategy-refresh-submissions",
type=int, type=int,
@@ -107,6 +121,41 @@ def build_parser() -> argparse.ArgumentParser:
default=os.getenv("MODELHUB_SUBMISSION_EXCLUSIONS_PATH", str(DEFAULT_SUBMISSION_EXCLUSIONS_PATH)), default=os.getenv("MODELHUB_SUBMISSION_EXCLUSIONS_PATH", str(DEFAULT_SUBMISSION_EXCLUSIONS_PATH)),
help=argparse.SUPPRESS, help=argparse.SUPPRESS,
) )
parser.add_argument(
"--market-intelligence-state-path",
default=os.getenv("MODELHUB_MARKET_INTELLIGENCE_PATH", str(DEFAULT_MARKET_INTELLIGENCE_PATH)),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-queue-refresh-seconds",
type=int,
default=int(os.getenv("MODELHUB_MARKET_QUEUE_REFRESH_SECONDS", str(DEFAULT_QUEUE_REFRESH_SECONDS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-framework-refresh-seconds",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FRAMEWORK_REFRESH_SECONDS", str(DEFAULT_FRAMEWORK_REFRESH_SECONDS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-throughput-window-hours",
type=int,
default=int(os.getenv("MODELHUB_MARKET_THROUGHPUT_WINDOW_HOURS", str(DEFAULT_THROUGHPUT_WINDOW_HOURS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-fetch-workers",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FETCH_WORKERS", str(DEFAULT_FETCH_WORKERS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-framework-min-samples",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FRAMEWORK_MIN_SAMPLES", str(DEFAULT_FRAMEWORK_MIN_SAMPLES))),
help=argparse.SUPPRESS,
)
parser.add_argument( parser.add_argument(
"--gpu-strategy-state-path", "--gpu-strategy-state-path",
default=os.getenv("MODELHUB_GPU_STRATEGY_STATE_PATH", str(DEFAULT_GPU_STRATEGY_PATH)), default=os.getenv("MODELHUB_GPU_STRATEGY_STATE_PATH", str(DEFAULT_GPU_STRATEGY_PATH)),
@@ -209,37 +258,90 @@ def choose_candidate_for_gpu(
template_selector: TemplateSelector, template_selector: TemplateSelector,
task_types: list[str], task_types: list[str],
target_gpu: str, target_gpu: str,
market_intelligence: MarketIntelligenceManager | None = None,
) -> CandidateModel | None: ) -> CandidateModel | None:
for task_type in task_types: for task_type in task_types:
supported_frameworks = template_selector.supported_frameworks_for_auto(task_type, target_gpu) supported_frameworks = template_selector.supported_frameworks_for_auto(task_type, target_gpu)
if not supported_frameworks: compatible_frameworks: list[str] = []
continue
try: try:
framework = choose_framework_for_task(task_type, target_gpu, supported_frameworks, inspection) if supported_frameworks:
template = template_selector.select_template(task_type, framework, target_gpu) compatible_frameworks = compatible_frameworks_for_task(
config_params = template_selector.render_config( task_type,
template, target_gpu,
gguf_filename=inspection.selected_gguf if framework == "llamacpp" else None, supported_frameworks,
) inspection,
)
except ValueError: except ValueError:
pass
if market_intelligence is not None:
discovered = market_intelligence.compatible_discovered_frameworks(
task_type=task_type,
target_gpu=target_gpu,
inspection=inspection,
)
compatible_frameworks.extend(
framework for framework in discovered if framework not in compatible_frameworks
)
if not compatible_frameworks:
continue continue
spec = TASK_SPEC_BY_TYPE[task_type] if market_intelligence is not None:
return CandidateModel( compatible_frameworks = market_intelligence.rank_frameworks(
repo_id=model.repo_id, task_type=task_type,
model_address=model.model_address, target_gpu=target_gpu,
pipeline_tag=model.pipeline_tag, compatible_frameworks=compatible_frameworks,
modality=spec.modality, )
task_type=task_type,
target_gpu=target_gpu, for framework in compatible_frameworks:
framework=framework, config_params = (
template_id=template.template_id, market_intelligence.official_config(
config_params=config_params, task_type=task_type,
downloads=model.downloads, target_gpu=target_gpu,
last_modified=model.last_modified, framework=framework,
gguf_filename=inspection.selected_gguf, gguf_filename=inspection.selected_gguf,
score=0.0, )
warnings=[], if market_intelligence is not None
) else None
)
if config_params is not None:
template_id = f"modelhub-live-{task_type}-{framework}-{target_gpu}".lower().replace("_", "-")
else:
try:
template = template_selector.select_template(task_type, framework, target_gpu)
config_params = template_selector.render_config(
template,
gguf_filename=inspection.selected_gguf if framework == "llamacpp" else None,
)
template_id = template.template_id
except (KeyError, ValueError):
continue
framework_metadata = (
market_intelligence.framework_metadata(task_type, target_gpu, framework)
if market_intelligence is not None
else {}
)
score = float(framework_metadata.get("frameworkCombinedScore") or 0.0)
warnings: list[str] = []
if bool(framework_metadata.get("frameworkEvidenceQualified", False)):
warnings.append("framework_selected_from_success_evidence")
if config_params is not None and bool(framework_metadata.get("frameworkOfficialConfigValid", False)):
warnings.append("official_build_config_synced")
spec = TASK_SPEC_BY_TYPE[task_type]
return CandidateModel(
repo_id=model.repo_id,
model_address=model.model_address,
pipeline_tag=model.pipeline_tag,
modality=spec.modality,
task_type=task_type,
target_gpu=target_gpu,
framework=framework,
template_id=template_id,
config_params=config_params,
downloads=model.downloads,
last_modified=model.last_modified,
gguf_filename=inspection.selected_gguf,
score=score,
warnings=warnings,
)
return None return None
@@ -284,6 +386,7 @@ def process_model_for_candidates(
allowed_task_types: list[str], allowed_task_types: list[str],
outcome_tracker: OutcomeTracker | None = None, outcome_tracker: OutcomeTracker | None = None,
submission_exclusion_store: SubmissionExclusionStore | None = None, submission_exclusion_store: SubmissionExclusionStore | None = None,
market_intelligence: MarketIntelligenceManager | None = None,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]: ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
specs = [spec for spec in task_specs_for_model(model) if spec.task_type in allowed_task_types] specs = [spec for spec in task_specs_for_model(model) if spec.task_type in allowed_task_types]
if not specs: if not specs:
@@ -345,11 +448,16 @@ def process_model_for_candidates(
template_selector=template_selector, template_selector=template_selector,
task_types=task_types, task_types=task_types,
target_gpu=target_gpu, target_gpu=target_gpu,
market_intelligence=market_intelligence,
) )
if best is None: if best is None:
skipped.append({"repoId": model.repo_id, "targetGpu": target_gpu, "reason": "no_compatible_auto_template_or_framework"}) skipped.append({"repoId": model.repo_id, "targetGpu": target_gpu, "reason": "no_compatible_auto_template_or_framework"})
continue continue
candidates.append(candidate_to_record(best)) record = candidate_to_record(best)
if market_intelligence is not None:
record.update(market_intelligence.gpu_metadata(target_gpu))
record.update(market_intelligence.framework_metadata(best.task_type, target_gpu, best.framework))
candidates.append(record)
return candidates, skipped, failed return candidates, skipped, failed
@@ -423,6 +531,7 @@ def collect_candidates_from_models(
outcome_tracker: OutcomeTracker | None, outcome_tracker: OutcomeTracker | None,
submission_exclusion_store: SubmissionExclusionStore | None, submission_exclusion_store: SubmissionExclusionStore | None,
read_concurrency: int, read_concurrency: int,
market_intelligence: MarketIntelligenceManager | None = None,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], int]: ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], int]:
candidates: list[dict[str, Any]] = [] candidates: list[dict[str, Any]] = []
skipped: list[dict[str, Any]] = [] skipped: list[dict[str, Any]] = []
@@ -451,6 +560,7 @@ def collect_candidates_from_models(
allowed_task_types=selected_task_types, allowed_task_types=selected_task_types,
outcome_tracker=outcome_tracker, outcome_tracker=outcome_tracker,
submission_exclusion_store=submission_exclusion_store, submission_exclusion_store=submission_exclusion_store,
market_intelligence=market_intelligence,
): index ): index
for index, model in enumerate(chunk) for index, model in enumerate(chunk)
} }
@@ -624,6 +734,8 @@ def run_submission(
) )
strategy_manager: GPUStrategyManager | None = None strategy_manager: GPUStrategyManager | None = None
strategy_summary: dict[str, Any] = {"enabled": False} strategy_summary: dict[str, Any] = {"enabled": False}
market_intelligence: MarketIntelligenceManager | None = None
market_summary: dict[str, Any] = {"enabled": False}
strategy_enabled = ( strategy_enabled = (
not bool(getattr(args, "disable_gpu_strategy", False)) not bool(getattr(args, "disable_gpu_strategy", False))
and not bool(getattr(args, "gpu", None) or getattr(args, "gpus", None)) and not bool(getattr(args, "gpu", None) or getattr(args, "gpus", None))
@@ -707,15 +819,19 @@ def run_submission(
"historyArchivePath": str(history_archive_path), "historyArchivePath": str(history_archive_path),
"historyArchiveRecordCount": len(archived_history), "historyArchiveRecordCount": len(archived_history),
"gpuStrategy": strategy_summary, "gpuStrategy": strategy_summary,
"marketIntelligence": market_summary,
"scanLimit": 0, "scanLimit": 0,
"candidateGoal": 0, "candidateGoal": 0,
"scanStages": [], "scanStages": [],
"scannedModels": 0, "scannedModels": 0,
"candidateCount": 0, "candidateCount": 0,
"candidateFrameworkCounts": {},
"targetSubmitCount": 0, "targetSubmitCount": 0,
"maxSubmitAttempts": 0, "maxSubmitAttempts": 0,
"plannedSubmitCount": 0, "plannedSubmitCount": 0,
"submittedCount": 0, "submittedCount": 0,
"submittedGpuCounts": {},
"submittedFrameworkCounts": {},
"duplicateCount": 0, "duplicateCount": 0,
"modelGpuUniquenessRejectedCount": 0, "modelGpuUniquenessRejectedCount": 0,
"skippedCount": 0, "skippedCount": 0,
@@ -728,12 +844,63 @@ def run_submission(
"outcomeSyncCount": synced_count, "outcomeSyncCount": synced_count,
} }
market_capable = (
hasattr(modelhub_client, "list_machine_info")
and hasattr(modelhub_client, "list_framework_stats")
and hasattr(modelhub_client, "list_tasks_page")
)
if not bool(getattr(args, "disable_market_intelligence", False)) and market_capable:
market_intelligence = MarketIntelligenceManager(
Path(getattr(args, "market_intelligence_state_path", DEFAULT_MARKET_INTELLIGENCE_PATH)),
queue_refresh_seconds=max(
60,
int(getattr(args, "market_queue_refresh_seconds", DEFAULT_QUEUE_REFRESH_SECONDS) or DEFAULT_QUEUE_REFRESH_SECONDS),
),
framework_refresh_seconds=max(
300,
int(
getattr(args, "market_framework_refresh_seconds", DEFAULT_FRAMEWORK_REFRESH_SECONDS)
or DEFAULT_FRAMEWORK_REFRESH_SECONDS
),
),
throughput_window_hours=max(
1,
int(
getattr(args, "market_throughput_window_hours", DEFAULT_THROUGHPUT_WINDOW_HOURS)
or DEFAULT_THROUGHPUT_WINDOW_HOURS
),
),
fetch_workers=max(
1,
int(getattr(args, "market_fetch_workers", DEFAULT_FETCH_WORKERS) or DEFAULT_FETCH_WORKERS),
),
framework_min_samples=max(
1,
int(
getattr(args, "market_framework_min_samples", DEFAULT_FRAMEWORK_MIN_SAMPLES)
or DEFAULT_FRAMEWORK_MIN_SAMPLES
),
),
)
market_intelligence.prepare(
modelhub_client,
supported_gpus=target_gpus,
task_types=selected_task_types,
now=now,
)
try:
market_intelligence.set_local_outcome_stats(outcome_tracker.get_stats_report())
except Exception:
market_intelligence.set_local_outcome_stats(None)
market_summary = market_intelligence.summary()
if strategy_enabled: if strategy_enabled:
strategy_manager = GPUStrategyManager( strategy_manager = GPUStrategyManager(
Path(getattr(args, "gpu_strategy_state_path", DEFAULT_GPU_STRATEGY_PATH)), Path(getattr(args, "gpu_strategy_state_path", DEFAULT_GPU_STRATEGY_PATH)),
refresh_submissions=max(1, int(getattr(args, "gpu_strategy_refresh_submissions", 200) or 200)), refresh_submissions=max(1, int(getattr(args, "gpu_strategy_refresh_submissions", 200) or 200)),
recent_terminal_window=max(1, int(getattr(args, "gpu_strategy_recent_window", 1000) or 1000)), recent_terminal_window=max(1, int(getattr(args, "gpu_strategy_recent_window", 1000) or 1000)),
long_term_min_samples=max(1, int(getattr(args, "gpu_strategy_min_long_samples", 100) or 100)), long_term_min_samples=max(1, int(getattr(args, "gpu_strategy_min_long_samples", 100) or 100)),
market_intelligence=market_intelligence,
) )
strategy_manager.prepare(modelhub_client, supported_gpus=target_gpus, now=now) strategy_manager.prepare(modelhub_client, supported_gpus=target_gpus, now=now)
strategy_summary = strategy_manager.summary() strategy_summary = strategy_manager.summary()
@@ -824,6 +991,7 @@ def run_submission(
outcome_tracker=outcome_tracker, outcome_tracker=outcome_tracker,
submission_exclusion_store=submission_exclusion_store, submission_exclusion_store=submission_exclusion_store,
read_concurrency=max(1, args.read_concurrency), read_concurrency=max(1, args.read_concurrency),
market_intelligence=market_intelligence,
) )
candidates.extend(stage_candidates) candidates.extend(stage_candidates)
skipped.extend(stage_skipped) skipped.extend(stage_skipped)
@@ -1027,6 +1195,9 @@ def run_submission(
outcome_tracker.save() outcome_tracker.save()
skip_reason_counts = Counter(str(item.get("reason") or "unknown") for item in skipped) skip_reason_counts = Counter(str(item.get("reason") or "unknown") for item in skipped)
failure_reason_counts = Counter(str(item.get("reason") or "unknown") for item in failed) failure_reason_counts = Counter(str(item.get("reason") or "unknown") for item in failed)
candidate_framework_counts = Counter(str(item.get("framework") or "unknown") for item in candidates)
submitted_gpu_counts = Counter(str(item.get("targetGpu") or "unknown") for item in submitted)
submitted_framework_counts = Counter(str(item.get("framework") or "unknown") for item in submitted)
summary = { summary = {
"generatedAt": now.isoformat(), "generatedAt": now.isoformat(),
@@ -1044,11 +1215,13 @@ def run_submission(
"historyArchivePath": str(history_archive_path), "historyArchivePath": str(history_archive_path),
"historyArchiveRecordCount": len(archived_history), "historyArchiveRecordCount": len(archived_history),
"gpuStrategy": strategy_summary, "gpuStrategy": strategy_summary,
"marketIntelligence": market_summary,
"scanLimit": scan_limit, "scanLimit": scan_limit,
"candidateGoal": candidate_goal, "candidateGoal": candidate_goal,
"scanStages": scan_stages, "scanStages": scan_stages,
"scannedModels": scanned_model_count, "scannedModels": scanned_model_count,
"candidateCount": len(candidates), "candidateCount": len(candidates),
"candidateFrameworkCounts": dict(candidate_framework_counts.most_common()),
"targetSubmitCount": target_submit_count, "targetSubmitCount": target_submit_count,
"maxSubmitAttempts": 0 if args.dry_run else min( "maxSubmitAttempts": 0 if args.dry_run else min(
len(diversified_candidates), len(diversified_candidates),
@@ -1058,6 +1231,8 @@ def run_submission(
"duplicateCount": len(duplicate_candidates), "duplicateCount": len(duplicate_candidates),
"modelGpuUniquenessRejectedCount": len(uniqueness_rejected_candidates), "modelGpuUniquenessRejectedCount": len(uniqueness_rejected_candidates),
"submittedCount": len(submitted), "submittedCount": len(submitted),
"submittedGpuCounts": dict(submitted_gpu_counts.most_common()),
"submittedFrameworkCounts": dict(submitted_framework_counts.most_common()),
"skippedCount": len(skipped), "skippedCount": len(skipped),
"skipReasonCounts": dict(skip_reason_counts.most_common()), "skipReasonCounts": dict(skip_reason_counts.most_common()),
"failedCount": len(failed), "failedCount": len(failed),

View File

@@ -0,0 +1,650 @@
from __future__ import annotations
import math
import re
import statistics
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Callable
from common import parse_datetime, read_json, utc_now, write_json
DEFAULT_MARKET_INTELLIGENCE_PATH = Path(".modelhub_state/market_intelligence.json")
MARKET_STATE_VERSION = 2
DEFAULT_QUEUE_REFRESH_SECONDS = 600
DEFAULT_FRAMEWORK_REFRESH_SECONDS = 21_600
DEFAULT_THROUGHPUT_WINDOW_HOURS = 6
DEFAULT_FETCH_WORKERS = 4
DEFAULT_FRAMEWORK_MIN_SAMPLES = 100
ERROR_RETRY_SECONDS = 300
def _clamp(value: float, minimum: float, maximum: float) -> float:
return max(minimum, min(maximum, value))
def _wilson_lower_bound(successes: int, total: int, *, z: float = 1.96) -> float:
if total <= 0:
return 0.0
probability = successes / total
z_squared = z * z
denominator = 1.0 + z_squared / total
centre = probability + z_squared / (2.0 * total)
margin = z * math.sqrt(
(probability * (1.0 - probability) / total) + z_squared / (4.0 * total * total)
)
return max(0.0, (centre - margin) / denominator)
def _fresh(timestamp: Any, *, now: datetime, ttl_seconds: int) -> bool:
parsed = parse_datetime(timestamp)
if parsed is None:
return False
return (now - parsed).total_seconds() < max(1, ttl_seconds)
def _page_total(client: Any, **kwargs: Any) -> int:
payload = client.list_tasks_page(current=1, page_size=1, only_mine=False, **kwargs)
data = payload.get("data") or {}
if not isinstance(data, dict) or "total" not in data:
raise RuntimeError("Public task count response is incomplete")
return max(0, int(data.get("total") or 0))
def _neutral_gpu_stats(gpus: list[str]) -> dict[str, dict[str, Any]]:
return {
gpu: {
"gpu": gpu,
"available": True,
"canVerify": None,
"maxConcurrentTasks": None,
"waiting": None,
"running": None,
"recentSuccess": None,
"recentTerminal": None,
"recentSuccessRate": None,
"recentWilsonLowerBound": None,
"throughputPerHour": None,
"backlogHours": None,
"queueFactor": 1.0,
"qualityFactor": 1.0,
"healthFactor": 1.0,
"queueWeight": 1.0,
"selectionWeight": 1.0,
"error": "market_data_unavailable",
}
for gpu in gpus
}
def _validated_official_config(config: str, *, framework: str, target_gpu: str) -> tuple[bool, str | None]:
if not isinstance(config, str) or not config.strip():
return False, "empty_config"
if len(config) > 200_000:
return False, "config_too_large"
if "sut_config:" not in config or "ref_config:" not in config:
return False, "missing_sut_or_ref_config"
if any(marker in config for marker in ("{{", "}}", "PLACEHOLDER")):
return False, "unresolved_placeholder"
declared = re.search(r"(?m)^\s*framework:\s*['\"]?([^'\"\s]+)", config)
if declared is None or declared.group(1).strip() != framework:
return False, "framework_mismatch"
if target_gpu == "Biren_166m":
gpu_counts = [int(value) for value in re.findall(r"\bgpu_num:\s*['\"]?(\d+)", config)]
parallel_counts = [
int(value)
for value in re.findall(
r"(?:-tp|--tensor-parallel-size)(?:\s+|,\s*|\n\s*-\s*)['\"]?(\d+)",
config,
)
]
if any(value > 1 for value in [*gpu_counts, *parallel_counts]):
return False, "biren_parallelism_exceeds_one"
return True, None
def _render_official_config(config: str, *, gguf_filename: str | None) -> str:
if not gguf_filename:
return config
return re.sub(
r"(?i)(/model/)[^,\]\s'\"]+\.gguf",
lambda match: f"{match.group(1)}{gguf_filename}",
config,
)
class MarketIntelligenceManager:
def __init__(
self,
path: Path | str = DEFAULT_MARKET_INTELLIGENCE_PATH,
*,
queue_refresh_seconds: int = DEFAULT_QUEUE_REFRESH_SECONDS,
framework_refresh_seconds: int = DEFAULT_FRAMEWORK_REFRESH_SECONDS,
throughput_window_hours: int = DEFAULT_THROUGHPUT_WINDOW_HOURS,
fetch_workers: int = DEFAULT_FETCH_WORKERS,
framework_min_samples: int = DEFAULT_FRAMEWORK_MIN_SAMPLES,
log_fn: Callable[[str], None] | None = None,
) -> None:
self.path = Path(path)
self.queue_refresh_seconds = max(60, int(queue_refresh_seconds))
self.framework_refresh_seconds = max(300, int(framework_refresh_seconds))
self.throughput_window_hours = max(1, int(throughput_window_hours))
self.fetch_workers = max(1, min(8, int(fetch_workers)))
self.framework_min_samples = max(1, int(framework_min_samples))
self.log = log_fn or (lambda message: print(message, flush=True))
self.state: dict[str, Any] | None = None
self.local_outcome_stats: dict[str, Any] = {}
self._last_framework_request_errors: set[tuple[str, str]] = set()
def set_local_outcome_stats(self, report: dict[str, Any] | None) -> None:
self.local_outcome_stats = report if isinstance(report, dict) else {}
def _load(self) -> dict[str, Any] | None:
try:
payload = read_json(self.path)
except (FileNotFoundError, ValueError):
return None
return payload if isinstance(payload, dict) else None
@staticmethod
def _compatible(state: dict[str, Any] | None, gpus: list[str], task_types: list[str]) -> bool:
if not state or int(state.get("version") or 0) != MARKET_STATE_VERSION:
return False
return list(state.get("supportedGpus") or []) == gpus and list(state.get("taskTypes") or []) == task_types
def _base_state(self, gpus: list[str], task_types: list[str], now: datetime) -> dict[str, Any]:
return {
"version": MARKET_STATE_VERSION,
"generatedAt": now.isoformat(),
"supportedGpus": gpus,
"taskTypes": task_types,
"queueUpdatedAt": None,
"frameworkUpdatedAt": None,
"queueAttemptedAt": None,
"frameworkAttemptedAt": None,
"throughputWindowHours": self.throughput_window_hours,
"gpuStats": _neutral_gpu_stats(gpus),
"frameworkStats": {},
"queueError": None,
"frameworkError": None,
}
def prepare(
self,
client: Any,
*,
supported_gpus: list[str],
task_types: list[str],
now: datetime | None = None,
) -> dict[str, Any]:
now = now or utc_now()
gpus = list(dict.fromkeys(gpu for gpu in supported_gpus if gpu))
tasks = list(dict.fromkeys(task for task in task_types if task))
loaded = self._load()
state = loaded if self._compatible(loaded, gpus, tasks) else self._base_state(gpus, tasks, now)
queue_due = not _fresh(
state.get("queueUpdatedAt"),
now=now,
ttl_seconds=self.queue_refresh_seconds,
)
framework_due = not _fresh(
state.get("frameworkUpdatedAt"),
now=now,
ttl_seconds=self.framework_refresh_seconds,
)
if state.get("queueError") and _fresh(
state.get("queueAttemptedAt"),
now=now,
ttl_seconds=min(ERROR_RETRY_SECONDS, self.queue_refresh_seconds),
):
queue_due = False
if state.get("frameworkError") and _fresh(
state.get("frameworkAttemptedAt"),
now=now,
ttl_seconds=min(ERROR_RETRY_SECONDS, self.framework_refresh_seconds),
):
framework_due = False
if queue_due:
state["queueAttemptedAt"] = now.isoformat()
try:
previous_gpu_stats = state.get("gpuStats") or {}
fresh_gpu_stats = self._fetch_gpu_stats(client, gpus=gpus, now=now)
for gpu, fresh_item in fresh_gpu_stats.items():
previous_item = previous_gpu_stats.get(gpu) or {}
if fresh_item.get("error") and previous_item and not previous_item.get("error"):
stale_item = dict(previous_item)
stale_item["stale"] = True
stale_item["refreshError"] = fresh_item.get("error")
fresh_gpu_stats[gpu] = stale_item
else:
fresh_item["stale"] = False
state["gpuStats"] = fresh_gpu_stats
state["queueUpdatedAt"] = now.isoformat()
state["queueError"] = None
except Exception as exc:
state["queueError"] = f"{type(exc).__name__}: {exc}"
if not state.get("gpuStats"):
state["gpuStats"] = _neutral_gpu_stats(gpus)
self.log(f"[market] queue_refresh_error reason={state['queueError']}")
if framework_due:
state["frameworkAttemptedAt"] = now.isoformat()
try:
previous_framework_stats = state.get("frameworkStats") or {}
fresh_framework_stats = self._fetch_framework_stats(client, gpus=gpus, task_types=tasks)
for task_type, gpu in self._last_framework_request_errors:
previous_rows = ((previous_framework_stats.get(task_type) or {}).get(gpu) or {})
if previous_rows:
fresh_framework_stats[task_type][gpu] = {
framework: {**dict(item), "stale": True}
for framework, item in previous_rows.items()
}
for task_type, by_gpu in fresh_framework_stats.items():
for gpu, rows in by_gpu.items():
previous_rows = ((previous_framework_stats.get(task_type) or {}).get(gpu) or {})
for framework, item in rows.items():
previous_item = previous_rows.get(framework) or {}
if not item.get("officialConfigValid") and previous_item.get("officialConfigValid"):
item["officialConfigValid"] = True
item["officialConfig"] = previous_item.get("officialConfig")
item["officialConfigStale"] = True
item["officialConfigRefreshError"] = item.get("officialConfigError")
state["frameworkStats"] = fresh_framework_stats
state["frameworkUpdatedAt"] = now.isoformat()
state["frameworkError"] = None
except Exception as exc:
state["frameworkError"] = f"{type(exc).__name__}: {exc}"
if not state.get("frameworkStats"):
state["frameworkStats"] = {}
self.log(f"[market] framework_refresh_error reason={state['frameworkError']}")
state["generatedAt"] = now.isoformat()
state["throughputWindowHours"] = self.throughput_window_hours
self.state = state
write_json(self.path, state)
self._log_snapshot("refreshed" if queue_due or framework_due else "loaded")
return state
def _fetch_gpu_stats(self, client: Any, *, gpus: list[str], now: datetime) -> dict[str, dict[str, Any]]:
local_end = now.astimezone()
local_begin = local_end - timedelta(hours=self.throughput_window_hours)
begin_text = local_begin.strftime("%Y-%m-%d %H:%M:%S")
end_text = local_end.strftime("%Y-%m-%d %H:%M:%S")
machine_by_gpu: dict[str, dict[str, Any]] = {}
for item in client.list_machine_info():
gpu = str(item.get("gpuType") or "")
if gpu:
machine_by_gpu[gpu] = item
def fetch_one(gpu: str) -> tuple[str, dict[str, Any]]:
waiting = _page_total(client, status="waiting", gpu_type=gpu)
running = _page_total(client, status="running", gpu_type=gpu)
completed = _page_total(
client,
status="success",
gpu_type=gpu,
begin_time=begin_text,
end_time=end_text,
)
success = _page_total(
client,
status="success",
verify_result=1,
gpu_type=gpu,
begin_time=begin_text,
end_time=end_text,
)
abnormal = _page_total(
client,
status="failed",
gpu_type=gpu,
begin_time=begin_text,
end_time=end_text,
)
terminal = max(success, completed + abnormal)
throughput = terminal / self.throughput_window_hours
backlog_hours = waiting / throughput if throughput > 0 else 9_999.0
success_rate = success / terminal if terminal else 0.0
machine = machine_by_gpu.get(gpu) or {}
return gpu, {
"gpu": gpu,
"available": machine.get("canVerify") is not False,
"canVerify": machine.get("canVerify"),
"maxConcurrentTasks": machine.get("maxConcurrentTasks"),
"waiting": waiting,
"running": running,
"recentSuccess": success,
"recentTerminal": terminal,
"recentSuccessRate": success_rate,
"recentWilsonLowerBound": _wilson_lower_bound(success, terminal),
"throughputPerHour": throughput,
"backlogHours": backlog_hours,
"error": None,
}
fetched: dict[str, dict[str, Any]] = {}
errors: dict[str, str] = {}
with ThreadPoolExecutor(max_workers=min(self.fetch_workers, max(1, len(gpus)))) as executor:
futures = {executor.submit(fetch_one, gpu): gpu for gpu in gpus}
for future in as_completed(futures):
gpu = futures[future]
try:
name, stats = future.result()
fetched[name] = stats
except Exception as exc:
errors[gpu] = f"{type(exc).__name__}: {exc}"
if not fetched and gpus:
raise RuntimeError("all GPU queue-stat requests failed")
finite_backlogs = [
float(stats["backlogHours"])
for stats in fetched.values()
if float(stats["backlogHours"]) < 9_999.0
]
median_backlog = statistics.median(finite_backlogs) if finite_backlogs else 24.0
max_wilson = max((float(stats["recentWilsonLowerBound"]) for stats in fetched.values()), default=0.0)
result = _neutral_gpu_stats(gpus)
for gpu, stats in fetched.items():
backlog = max(0.25, float(stats["backlogHours"]))
queue_factor = _clamp(math.sqrt(max(0.25, median_backlog) / backlog), 0.35, 1.75)
wilson = float(stats["recentWilsonLowerBound"])
quality_factor = 1.0 if max_wilson <= 0 else 0.6 + 1.4 * (wilson / max_wilson)
health_factor = 1.0
if stats.get("canVerify") is False:
health_factor = 0.05
elif (
int(stats.get("running") or 0) == 0
and int(stats.get("waiting") or 0) > 0
and int(stats.get("maxConcurrentTasks") or 0) <= 0
and int(stats.get("recentSuccess") or 0) == 0
):
health_factor = 0.10
elif (
int(stats.get("running") or 0) == 0
and int(stats.get("waiting") or 0) > 0
and int(stats.get("recentTerminal") or 0) == 0
):
health_factor = 0.25
stats["queueFactor"] = queue_factor
stats["qualityFactor"] = quality_factor
stats["healthFactor"] = health_factor
stats["queueWeight"] = _clamp(queue_factor * health_factor, 0.05, 2.0)
stats["selectionWeight"] = _clamp(queue_factor * quality_factor * health_factor, 0.05, 3.0)
result[gpu] = stats
for gpu, error in errors.items():
result[gpu]["error"] = error
return result
def _fetch_framework_stats(
self,
client: Any,
*,
gpus: list[str],
task_types: list[str],
) -> dict[str, dict[str, dict[str, dict[str, Any]]]]:
self._last_framework_request_errors = set()
result: dict[str, dict[str, dict[str, dict[str, Any]]]] = {
task_type: {gpu: {} for gpu in gpus}
for task_type in task_types
}
def fetch_one(task_type: str, gpu: str) -> tuple[str, str, list[dict[str, Any]]]:
return task_type, gpu, client.list_framework_stats(task_type, gpu)
with ThreadPoolExecutor(max_workers=self.fetch_workers) as executor:
futures = {
executor.submit(fetch_one, task_type, gpu): (task_type, gpu)
for task_type in task_types
for gpu in gpus
}
successful_requests = 0
errors: list[str] = []
for future in as_completed(futures):
task_type, gpu = futures[future]
try:
_, _, rows = future.result()
successful_requests += 1
except Exception as exc:
errors.append(f"{task_type}/{gpu}: {type(exc).__name__}: {exc}")
self._last_framework_request_errors.add((task_type, gpu))
continue
for row in rows:
framework = str(row.get("framework") or "").strip()
if not framework:
continue
total = max(0, int(row.get("modelCount") or 0))
success = max(0, min(total, int(row.get("successCount") or 0)))
result[task_type][gpu][framework] = {
"framework": framework,
"modelCount": total,
"successCount": success,
"successRate": success / total if total else 0.0,
"wilsonLowerBound": _wilson_lower_bound(success, total),
}
if futures and successful_requests <= 0:
detail = errors[0] if errors else "unknown error"
raise RuntimeError(f"all framework-stat requests failed ({detail})")
if hasattr(client, "get_build_config"):
config_targets = [
(task_type, gpu, framework)
for task_type, by_gpu in result.items()
for gpu, by_framework in by_gpu.items()
for framework in by_framework
]
def fetch_config(task_type: str, gpu: str, framework: str) -> tuple[str, str, str, str]:
return task_type, gpu, framework, client.get_build_config(task_type, gpu, framework)
with ThreadPoolExecutor(max_workers=self.fetch_workers) as executor:
config_futures = {
executor.submit(fetch_config, task_type, gpu, framework): (task_type, gpu, framework)
for task_type, gpu, framework in config_targets
}
for future in as_completed(config_futures):
task_type, gpu, framework = config_futures[future]
item = result[task_type][gpu][framework]
try:
_, _, _, config = future.result()
valid, reason = _validated_official_config(
config,
framework=framework,
target_gpu=gpu,
)
item["officialConfigValid"] = valid
item["officialConfigError"] = reason
if valid:
item["officialConfig"] = config
except Exception as exc:
item["officialConfigValid"] = False
item["officialConfigError"] = f"{type(exc).__name__}: {exc}"
return result
def gpu_weight(self, gpu: str, *, category: str) -> float:
stats = ((self.state or {}).get("gpuStats") or {}).get(gpu) or {}
if category == "all_supported":
return max(0.05, float(stats.get("queueWeight") or 1.0))
return max(0.05, float(stats.get("selectionWeight") or 1.0))
def gpu_metadata(self, gpu: str) -> dict[str, Any]:
stats = ((self.state or {}).get("gpuStats") or {}).get(gpu) or {}
return {
"marketWeight": float(stats.get("selectionWeight") or 1.0),
"queueWeight": float(stats.get("queueWeight") or 1.0),
"queueWaiting": stats.get("waiting"),
"queueRunning": stats.get("running"),
"queueBacklogHours": stats.get("backlogHours"),
"publicRecentSuccessRate": stats.get("recentSuccessRate"),
"publicThroughputPerHour": stats.get("throughputPerHour"),
"marketDataStale": bool(stats.get("stale", False)),
}
def rank_frameworks(
self,
*,
task_type: str,
target_gpu: str,
compatible_frameworks: list[str],
) -> list[str]:
stats = (
(((self.state or {}).get("frameworkStats") or {}).get(task_type) or {}).get(target_gpu)
or {}
)
legacy_index = {framework: index for index, framework in enumerate(compatible_frameworks)}
def rank_key(framework: str) -> tuple[int, float, int, int]:
evidence = self._framework_evidence(task_type, target_gpu, framework)
return (
1 if evidence["qualified"] else 0,
float(evidence["combinedScore"]) if evidence["qualified"] else 0.0,
int(evidence["publicSamples"]) + int(evidence["localSamples"]),
-legacy_index[framework],
)
return sorted(compatible_frameworks, key=rank_key, reverse=True)
def _framework_evidence(self, task_type: str, target_gpu: str, framework: str) -> dict[str, Any]:
public_item = (
(((self.state or {}).get("frameworkStats") or {}).get(task_type) or {}).get(target_gpu)
or {}
).get(framework) or {}
public_samples = max(0, int(public_item.get("modelCount") or 0))
public_score = float(public_item.get("wilsonLowerBound") or 0.0)
local_key = f"{target_gpu}|{framework}|{task_type}"
local_item = (self.local_outcome_stats.get("combinationStats") or {}).get(local_key) or {}
local_success = max(0, int(local_item.get("successCount") or 0))
local_failure = max(0, int(local_item.get("failureCount") or 0))
local_samples = local_success + local_failure
local_score = _wilson_lower_bound(local_success, local_samples)
public_qualified = public_samples >= self.framework_min_samples
local_qualified = local_samples >= 20
if public_qualified:
local_weight = min(0.35, local_samples / (local_samples + 50.0)) if local_samples >= 5 else 0.0
combined = public_score * (1.0 - local_weight) + local_score * local_weight
elif local_qualified:
combined = local_score
else:
combined = 0.0
return {
"qualified": public_qualified or local_qualified,
"publicQualified": public_qualified,
"localQualified": local_qualified,
"combinedScore": combined,
"publicSamples": public_samples,
"publicScore": public_score,
"localSamples": local_samples,
"localSuccessRate": local_success / local_samples if local_samples else None,
"localScore": local_score if local_samples else None,
}
def compatible_discovered_frameworks(
self,
*,
task_type: str,
target_gpu: str,
inspection: Any,
) -> list[str]:
stats = (
(((self.state or {}).get("frameworkStats") or {}).get(task_type) or {}).get(target_gpu)
or {}
)
compatible: list[str] = []
for framework, item in stats.items():
if not bool(item.get("officialConfigValid", False)):
continue
normalized = framework.lower()
if "llamacpp" in normalized or "gguf" in normalized:
usable = bool(getattr(inspection, "has_gguf", False))
elif "onnx" in normalized or "sherpa" in normalized:
usable = bool(getattr(inspection, "has_onnx_weights", False))
elif task_type in {"text-generation", "visual-multi-modal", "reinforcement_learning"}:
usable = bool(getattr(inspection, "has_vllm_weights", False))
else:
usable = bool(getattr(inspection, "has_standard_weights", False))
if usable:
compatible.append(framework)
return compatible
def official_config(
self,
*,
task_type: str,
target_gpu: str,
framework: str,
gguf_filename: str | None = None,
) -> str | None:
item = (
(((self.state or {}).get("frameworkStats") or {}).get(task_type) or {}).get(target_gpu)
or {}
).get(framework) or {}
if not bool(item.get("officialConfigValid", False)):
return None
config = item.get("officialConfig")
if not isinstance(config, str) or not config:
return None
return _render_official_config(config, gguf_filename=gguf_filename)
def framework_metadata(self, task_type: str, target_gpu: str, framework: str) -> dict[str, Any]:
item = (
(((self.state or {}).get("frameworkStats") or {}).get(task_type) or {}).get(target_gpu)
or {}
).get(framework) or {}
evidence = self._framework_evidence(task_type, target_gpu, framework)
return {
"frameworkMarketSamples": int(item.get("modelCount") or 0),
"frameworkMarketSuccessRate": item.get("successRate"),
"frameworkMarketWilsonLowerBound": item.get("wilsonLowerBound"),
"frameworkLocalSamples": evidence["localSamples"],
"frameworkLocalSuccessRate": evidence["localSuccessRate"],
"frameworkCombinedScore": evidence["combinedScore"],
"frameworkMarketQualified": evidence["publicQualified"],
"frameworkLocalQualified": evidence["localQualified"],
"frameworkEvidenceQualified": evidence["qualified"],
"frameworkOfficialConfigValid": bool(item.get("officialConfigValid", False)),
"frameworkOfficialConfigStale": bool(item.get("officialConfigStale", False)),
"frameworkConfigSource": "modelhub_live" if item.get("officialConfigValid") else "local_template",
}
def _log_snapshot(self, action: str) -> None:
if self.state is None:
return
stats = self.state.get("gpuStats") or {}
ranked = sorted(
stats.values(),
key=lambda item: -float(item.get("selectionWeight") or 0.0),
)
leaders = ",".join(
f"{item.get('gpu')}:{float(item.get('selectionWeight') or 0.0):.2f}"
for item in ranked[:3]
)
self.log(
f"[market] {action} queue_at={self.state.get('queueUpdatedAt') or 'n/a'} "
f"framework_at={self.state.get('frameworkUpdatedAt') or 'n/a'} leaders={leaders or 'n/a'}"
)
def summary(self) -> dict[str, Any]:
if self.state is None:
return {"enabled": False}
return {
"enabled": True,
"statePath": str(self.path),
"queueUpdatedAt": self.state.get("queueUpdatedAt"),
"frameworkUpdatedAt": self.state.get("frameworkUpdatedAt"),
"throughputWindowHours": int(self.state.get("throughputWindowHours") or self.throughput_window_hours),
"queueError": self.state.get("queueError"),
"frameworkError": self.state.get("frameworkError"),
"gpuStats": dict(self.state.get("gpuStats") or {}),
}

View File

@@ -85,11 +85,18 @@ class ModelHubClient:
current: int = 1, current: int = 1,
page_size: int = 50, page_size: int = 50,
only_mine: bool = True, only_mine: bool = True,
begin_time: datetime | None = None, begin_time: datetime | str | None = None,
end_time: datetime | None = None, end_time: datetime | str | None = None,
gpu_type: str | None = None, gpu_type: str | None = None,
model_id: str | None = None, model_id: str | None = None,
status: str | None = None,
verify_result: int | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
def query_time(value: datetime | str | None) -> str | None:
if isinstance(value, str):
return value
return format_modelhub_datetime(value) if value else None
return self._request( return self._request(
"GET", "GET",
"/api/adapt/task/page", "/api/adapt/task/page",
@@ -97,13 +104,44 @@ class ModelHubClient:
"current": current, "current": current,
"pageSize": page_size, "pageSize": page_size,
"onlyMine": str(only_mine).lower(), "onlyMine": str(only_mine).lower(),
"beginTime": format_modelhub_datetime(begin_time) if begin_time else None, "beginTime": query_time(begin_time),
"endTime": format_modelhub_datetime(end_time) if end_time else None, "endTime": query_time(end_time),
"gpuType": gpu_type, "gpuType": gpu_type,
"modelId": model_id, "modelId": model_id,
"status": status,
"verifyResult": verify_result,
}, },
) )
def list_machine_info(self) -> list[dict[str, Any]]:
payload = self._request("GET", "/api/computility/power/machine/list/machine-info")
data = payload.get("data") or []
if not isinstance(data, list):
raise ModelHubAPIError("Machine info response is invalid", payload=payload)
return [item for item in data if isinstance(item, dict)]
def list_framework_stats(self, task_type: str, target_gpu: str) -> list[dict[str, Any]]:
payload = self._request(
"GET",
"/api/computility/driver/images/frameworks",
query={"taskType": task_type, "gpuTypeName": target_gpu},
)
data = payload.get("data") or []
if not isinstance(data, list):
raise ModelHubAPIError("Framework statistics response is invalid", payload=payload)
return [item for item in data if isinstance(item, dict)]
def get_build_config(self, task_type: str, target_gpu: str, framework: str) -> str:
payload = self._request(
"POST",
"/api/adapt/task/build-config",
query={"taskType": task_type, "gpuType": target_gpu, "framework": framework},
)
data = payload.get("data")
if not isinstance(data, str) or not data.strip():
raise ModelHubAPIError("Official build config response is invalid", payload=payload)
return data
def list_tasks( def list_tasks(
self, self,
*, *,
@@ -747,6 +785,15 @@ class ModelHubClientPool:
"""Single-page task listing via the reader client (no fanout).""" """Single-page task listing via the reader client (no fanout)."""
return self._reader.list_tasks_page(**kwargs) return self._reader.list_tasks_page(**kwargs)
def list_machine_info(self) -> list[dict[str, Any]]:
return self._reader.list_machine_info()
def list_framework_stats(self, task_type: str, target_gpu: str) -> list[dict[str, Any]]:
return self._reader.list_framework_stats(task_type, target_gpu)
def get_build_config(self, task_type: str, target_gpu: str, framework: str) -> str:
return self._reader.get_build_config(task_type, target_gpu, framework)
def find_recent_task_id(self, model_id: str, gpu_type: str, submitted_after: datetime) -> str | None: def find_recent_task_id(self, model_id: str, gpu_type: str, submitted_after: datetime) -> str | None:
for client in self.clients: for client in self.clients:
task_id = client.find_recent_task_id(model_id, gpu_type, submitted_after) task_id = client.find_recent_task_id(model_id, gpu_type, submitted_after)

View File

@@ -13,6 +13,14 @@ from daily_runner import DEFAULT_DAILY_RUNS_DIR, log, run_daily_batches
from gpu_strategy import DEFAULT_GPU_STRATEGY_PATH from gpu_strategy import DEFAULT_GPU_STRATEGY_PATH
from hf_discovery import HuggingFaceDiscovery from hf_discovery import HuggingFaceDiscovery
from main import DEFAULT_LEDGER_PATH, DEFAULT_RUNS_DIR, make_run_dir from main import DEFAULT_LEDGER_PATH, DEFAULT_RUNS_DIR, make_run_dir
from market_intelligence import (
DEFAULT_FETCH_WORKERS,
DEFAULT_FRAMEWORK_MIN_SAMPLES,
DEFAULT_FRAMEWORK_REFRESH_SECONDS,
DEFAULT_MARKET_INTELLIGENCE_PATH,
DEFAULT_QUEUE_REFRESH_SECONDS,
DEFAULT_THROUGHPUT_WINDOW_HOURS,
)
from modelhub_client import DEFAULT_CAPACITY_STATE_PATH, ModelHubClient, ModelHubClientPool from modelhub_client import DEFAULT_CAPACITY_STATE_PATH, ModelHubClient, ModelHubClientPool
from outcome_tracker import DEFAULT_OUTCOMES_PATH, OutcomeTracker from outcome_tracker import DEFAULT_OUTCOMES_PATH, OutcomeTracker
from runner_common import DEFAULT_KEY_PATH, ensure_tokens from runner_common import DEFAULT_KEY_PATH, ensure_tokens
@@ -60,6 +68,11 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument("--skip-history-archive", action="store_true", help="Skip historical task archive download for this run") parser.add_argument("--skip-history-archive", action="store_true", help="Skip historical task archive download for this run")
parser.add_argument("--dry-run", action="store_true", help="Plan the day without creating tasks") parser.add_argument("--dry-run", action="store_true", help="Plan the day without creating tasks")
parser.add_argument("--disable-gpu-strategy", action="store_true", help="Disable adaptive 50/30/20 GPU scheduling") parser.add_argument("--disable-gpu-strategy", action="store_true", help="Disable adaptive 50/30/20 GPU scheduling")
parser.add_argument(
"--disable-market-intelligence",
action="store_true",
help="Disable live queue/throughput and public framework statistics",
)
parser.add_argument( parser.add_argument(
"--gpu-strategy-refresh-submissions", "--gpu-strategy-refresh-submissions",
type=int, type=int,
@@ -88,6 +101,41 @@ def build_parser() -> argparse.ArgumentParser:
) )
parser.add_argument("--gpu-strategy-recent-window", type=int, default=1000, help=argparse.SUPPRESS) parser.add_argument("--gpu-strategy-recent-window", type=int, default=1000, help=argparse.SUPPRESS)
parser.add_argument("--gpu-strategy-min-long-samples", type=int, default=100, help=argparse.SUPPRESS) parser.add_argument("--gpu-strategy-min-long-samples", type=int, default=100, help=argparse.SUPPRESS)
parser.add_argument(
"--market-intelligence-state-path",
default=os.getenv("MODELHUB_MARKET_INTELLIGENCE_PATH", str(DEFAULT_MARKET_INTELLIGENCE_PATH)),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-queue-refresh-seconds",
type=int,
default=int(os.getenv("MODELHUB_MARKET_QUEUE_REFRESH_SECONDS", str(DEFAULT_QUEUE_REFRESH_SECONDS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-framework-refresh-seconds",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FRAMEWORK_REFRESH_SECONDS", str(DEFAULT_FRAMEWORK_REFRESH_SECONDS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-throughput-window-hours",
type=int,
default=int(os.getenv("MODELHUB_MARKET_THROUGHPUT_WINDOW_HOURS", str(DEFAULT_THROUGHPUT_WINDOW_HOURS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-fetch-workers",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FETCH_WORKERS", str(DEFAULT_FETCH_WORKERS))),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--market-framework-min-samples",
type=int,
default=int(os.getenv("MODELHUB_MARKET_FRAMEWORK_MIN_SAMPLES", str(DEFAULT_FRAMEWORK_MIN_SAMPLES))),
help=argparse.SUPPRESS,
)
parser.add_argument( parser.add_argument(
"--capacity-state-path", "--capacity-state-path",
default=os.getenv("MODELHUB_CAPACITY_STATE_PATH", str(DEFAULT_CAPACITY_STATE_PATH)), default=os.getenv("MODELHUB_CAPACITY_STATE_PATH", str(DEFAULT_CAPACITY_STATE_PATH)),

View File

@@ -63,58 +63,69 @@ def task_specs_for_model(model: HFModelSummary) -> list[TaskSpec]:
return [task for task in TASK_SPECS if pipeline_tag in task.pipeline_tags] return [task for task in TASK_SPECS if pipeline_tag in task.pipeline_tags]
def choose_text_generation_framework(target_gpu: str, supported_frameworks: set[str], inspection: ModelInspection) -> str: def compatible_text_generation_frameworks(
target_gpu: str,
supported_frameworks: set[str],
inspection: ModelInspection,
) -> list[str]:
can_llamacpp = "llamacpp" in supported_frameworks can_llamacpp = "llamacpp" in supported_frameworks
vllm_like = [framework for framework in VLLM_LIKE_FRAMEWORKS if framework in supported_frameworks] vllm_like = [framework for framework in VLLM_LIKE_FRAMEWORKS if framework in supported_frameworks]
can_transformers = "transformers" in supported_frameworks can_transformers = "transformers" in supported_frameworks
compatible: list[str] = []
if can_llamacpp and vllm_like: if can_llamacpp and inspection.has_gguf:
if inspection.has_gguf: compatible.append("llamacpp")
return "llamacpp" if inspection.has_vllm_weights:
if inspection.has_vllm_weights: compatible.extend(vllm_like)
return vllm_like[0] if can_transformers:
raise ValueError(f"{target_gpu} supports both llamacpp and vLLM-like frameworks, but the model lacks usable weights") compatible.append("transformers")
if can_llamacpp: if not compatible:
if inspection.has_gguf: raise ValueError(f"No compatible LLM weights/framework combination is available for {target_gpu}")
return "llamacpp" return compatible
raise ValueError(f"{target_gpu} only supports llamacpp for this workflow, but no usable GGUF was found")
if vllm_like:
if inspection.has_vllm_weights:
return vllm_like[0]
raise ValueError(f"{target_gpu} only supports vLLM-like frameworks for this workflow, but no standard weights were found")
if can_transformers:
if inspection.has_vllm_weights:
return "transformers"
raise ValueError(f"{target_gpu} only supports transformers for this workflow, but no standard weights were found")
raise ValueError(f"No supported LLM framework template is available for {target_gpu}")
def choose_framework_for_task(task_type: str, target_gpu: str, supported_frameworks: set[str], inspection: ModelInspection) -> str: def choose_text_generation_framework(target_gpu: str, supported_frameworks: set[str], inspection: ModelInspection) -> str:
return compatible_text_generation_frameworks(target_gpu, supported_frameworks, inspection)[0]
def compatible_frameworks_for_task(
task_type: str,
target_gpu: str,
supported_frameworks: set[str],
inspection: ModelInspection,
) -> list[str]:
if task_type in {"text-generation", "visual-multi-modal", "reinforcement_learning"}: if task_type in {"text-generation", "visual-multi-modal", "reinforcement_learning"}:
return choose_text_generation_framework(target_gpu, supported_frameworks, inspection) return compatible_text_generation_frameworks(target_gpu, supported_frameworks, inspection)
if task_type == "asr": if task_type == "asr":
compatible: list[str] = []
if "sherpa-onnx" in supported_frameworks and inspection.has_onnx_weights: if "sherpa-onnx" in supported_frameworks and inspection.has_onnx_weights:
return "sherpa-onnx" compatible.append("sherpa-onnx")
for framework in ("transformers", "funasr"): if inspection.has_standard_weights:
if framework in supported_frameworks and inspection.has_standard_weights: compatible.extend(framework for framework in ("transformers", "funasr") if framework in supported_frameworks)
return framework if compatible:
return compatible
raise ValueError(f"No compatible ASR framework found for {target_gpu}") raise ValueError(f"No compatible ASR framework found for {target_gpu}")
if task_type == "feature_emb": if task_type == "feature_emb":
for framework in ("sentence-transformers", "transformers"): if inspection.has_standard_weights:
if framework in supported_frameworks and inspection.has_standard_weights: compatible = [framework for framework in ("sentence-transformers", "transformers") if framework in supported_frameworks]
return framework if compatible:
return compatible
raise ValueError(f"No compatible embedding framework found for {target_gpu}") raise ValueError(f"No compatible embedding framework found for {target_gpu}")
if task_type in {"question_answering", "vision_classification", "text_classification"}: if task_type in {"question_answering", "vision_classification", "text_classification"}:
if "transformers" in supported_frameworks and inspection.has_standard_weights: if "transformers" in supported_frameworks and inspection.has_standard_weights:
return "transformers" return ["transformers"]
raise ValueError(f"No compatible transformers template found for {task_type} on {target_gpu}") raise ValueError(f"No compatible transformers template found for {task_type} on {target_gpu}")
if task_type == "text-to-image-generation": if task_type == "text-to-image-generation":
if "diffusers" in supported_frameworks and inspection.has_standard_weights: if "diffusers" in supported_frameworks and inspection.has_standard_weights:
return "diffusers" return ["diffusers"]
raise ValueError(f"No compatible diffusers template found for {target_gpu}") raise ValueError(f"No compatible diffusers template found for {target_gpu}")
raise ValueError(f"Unsupported task type for auto framework selection: {task_type}") raise ValueError(f"Unsupported task type for auto framework selection: {task_type}")
def choose_framework_for_task(task_type: str, target_gpu: str, supported_frameworks: set[str], inspection: ModelInspection) -> str:
return compatible_frameworks_for_task(task_type, target_gpu, supported_frameworks, inspection)[0]

View File

@@ -1 +1 @@
AGENT_VERSION = "2026.08.02.5" AGENT_VERSION = "2026.08.04.1"

View File

@@ -49,6 +49,15 @@ class HistoryClient:
return list(self.tasks) return list(self.tasks)
class WeightedMarket:
def gpu_weight(self, gpu: str, *, category: str) -> float:
del category
return {"gpu-a": 3.0, "gpu-b": 0.1, "gpu-c": 0.1, "gpu-d": 0.1}[gpu]
def gpu_metadata(self, gpu: str) -> dict:
return {"marketWeight": self.gpu_weight(gpu, category=ALL_SUPPORTED)}
class GPUStrategyTests(unittest.TestCase): class GPUStrategyTests(unittest.TestCase):
def test_long_term_ranking_rejects_tiny_high_rate_sample(self) -> None: def test_long_term_ranking_rejects_tiny_high_rate_sample(self) -> None:
start = datetime(2026, 1, 1, tzinfo=timezone.utc) start = datetime(2026, 1, 1, tzinfo=timezone.utc)
@@ -117,6 +126,28 @@ class GPUStrategyTests(unittest.TestCase):
self.assertEqual(200, manager.state["acceptedTotal"]) self.assertEqual(200, manager.state["acceptedTotal"])
self.assertEqual(0, manager.state["acceptedSinceRefresh"]) self.assertEqual(0, manager.state["acceptedSinceRefresh"])
def test_market_weights_change_gpu_mix_without_changing_50_30_20_split(self) -> None:
supported = ["gpu-a", "gpu-b", "gpu-c", "gpu-d"]
manager = GPUStrategyManager("unused.json", market_intelligence=WeightedMarket())
manager.state = build_strategy_snapshot([], supported_gpus=supported)
candidates = [
{"repoId": f"owner/model-{model}", "targetGpu": gpu}
for model in range(300)
for gpu in supported
]
ordered = manager.order_candidates(candidates)[:200]
category_counts = Counter(candidate["strategyCategory"] for candidate in ordered)
exploration_counts = Counter(
candidate["targetGpu"]
for candidate in ordered
if candidate["strategyCategory"] == ALL_SUPPORTED
)
self.assertEqual({LONG_TERM: 100, ALL_SUPPORTED: 60, RECENT: 40}, category_counts)
self.assertGreater(exploration_counts["gpu-a"], exploration_counts["gpu-b"])
self.assertTrue(all(exploration_counts[gpu] > 0 for gpu in supported))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -0,0 +1,337 @@
from __future__ import annotations
import sys
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
PACKAGE_DIR = Path(__file__).resolve().parents[1] / "modelhub_submmit_api"
if str(PACKAGE_DIR) in sys.path:
sys.path.remove(str(PACKAGE_DIR))
sys.path.insert(0, str(PACKAGE_DIR))
from main import choose_candidate_for_gpu # noqa: E402
from market_intelligence import MarketIntelligenceManager # noqa: E402
from models import HFModelSummary, ModelInspection # noqa: E402
from template_selector import TemplateSelector # noqa: E402
class PublicMarketClient:
def __init__(self) -> None:
self.calls = 0
self.counts = {
"fast": {"waiting": 100, "running": 8, "completed": 120, "success": 80, "failed": 0},
"slow": {"waiting": 10, "running": 1, "completed": 1, "success": 1, "failed": 0},
"stopped": {"waiting": 500, "running": 0, "completed": 0, "success": 0, "failed": 0},
}
def list_machine_info(self) -> list[dict]:
self.calls += 1
return [
{"gpuType": "fast", "canVerify": True, "maxConcurrentTasks": 8},
{"gpuType": "slow", "canVerify": True, "maxConcurrentTasks": 1},
{"gpuType": "stopped", "canVerify": False, "maxConcurrentTasks": 0},
]
def list_tasks_page(self, **kwargs) -> dict: # noqa: ANN003
self.calls += 1
row = self.counts[kwargs["gpu_type"]]
status = kwargs.get("status")
verify_result = kwargs.get("verify_result")
if status == "waiting":
total = row["waiting"]
elif status == "running":
total = row["running"]
elif status == "failed":
total = row["failed"]
elif status == "success" and verify_result == 1:
total = row["success"]
else:
total = row["completed"]
return {"data": {"total": total, "records": []}}
def list_framework_stats(self, task_type: str, target_gpu: str) -> list[dict]:
self.calls += 1
del task_type, target_gpu
return [
{"framework": "vllm", "modelCount": 1000, "successCount": 100},
{"framework": "transformers", "modelCount": 1000, "successCount": 300},
]
def get_build_config(self, task_type: str, target_gpu: str, framework: str) -> str:
self.calls += 1
del task_type, target_gpu
return (
f"framework: {framework}\n"
"sut_config:\n gpu_num: 1\n values: {}\n"
"ref_config:\n gpu_num: 1\n values: {}\n"
)
class FailingMarketClient:
def __init__(self) -> None:
self.calls = 0
def list_machine_info(self) -> list[dict]:
self.calls += 1
raise RuntimeError("temporary queue outage")
def list_tasks_page(self, **_kwargs) -> dict: # noqa: ANN003
self.calls += 1
raise RuntimeError("temporary queue outage")
def list_framework_stats(self, task_type: str, target_gpu: str) -> list[dict]:
self.calls += 1
del task_type, target_gpu
raise RuntimeError("temporary framework outage")
class MarketIntelligenceTests(unittest.TestCase):
def test_expected_throughput_beats_short_raw_queue_and_stopped_gpu_is_demoted(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
client = PublicMarketClient()
manager = MarketIntelligenceManager(
Path(temporary_dir) / "market.json",
throughput_window_hours=6,
log_fn=lambda _message: None,
)
state = manager.prepare(
client,
supported_gpus=["fast", "slow", "stopped"],
task_types=["text-generation"],
now=datetime(2026, 8, 4, tzinfo=timezone.utc),
)
fast = state["gpuStats"]["fast"]
slow = state["gpuStats"]["slow"]
stopped = state["gpuStats"]["stopped"]
self.assertGreater(fast["throughputPerHour"], slow["throughputPerHour"])
self.assertLess(fast["backlogHours"], slow["backlogHours"])
self.assertGreater(fast["selectionWeight"], slow["selectionWeight"])
self.assertLessEqual(stopped["selectionWeight"], 0.1)
self.assertTrue(
state["frameworkStats"]["text-generation"]["fast"]["transformers"]["officialConfigValid"]
)
def test_snapshot_cache_prevents_repeated_public_api_scans(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
client = PublicMarketClient()
manager = MarketIntelligenceManager(Path(temporary_dir) / "market.json", log_fn=lambda _message: None)
started = datetime(2026, 8, 4, tzinfo=timezone.utc)
manager.prepare(
client,
supported_gpus=["fast", "slow"],
task_types=["text-generation"],
now=started,
)
first_call_count = client.calls
manager.prepare(
client,
supported_gpus=["fast", "slow"],
task_types=["text-generation"],
now=started + timedelta(seconds=30),
)
self.assertEqual(first_call_count, client.calls)
def test_market_outage_falls_back_to_neutral_and_uses_retry_backoff(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
client = FailingMarketClient()
manager = MarketIntelligenceManager(Path(temporary_dir) / "market.json", log_fn=lambda _message: None)
started = datetime(2026, 8, 4, tzinfo=timezone.utc)
state = manager.prepare(
client,
supported_gpus=["gpu"],
task_types=["text-generation"],
now=started,
)
self.assertEqual(1.0, state["gpuStats"]["gpu"]["selectionWeight"])
self.assertIsNotNone(state["queueError"])
self.assertIsNotNone(state["frameworkError"])
first_call_count = client.calls
manager.prepare(
client,
supported_gpus=["gpu"],
task_types=["text-generation"],
now=started + timedelta(seconds=30),
)
self.assertEqual(first_call_count, client.calls)
def test_framework_ranking_uses_confidence_bound_and_ignores_tiny_samples(self) -> None:
manager = MarketIntelligenceManager("unused.json", framework_min_samples=100)
manager.state = {
"frameworkStats": {
"text-generation": {
"gpu": {
"vllm": {"modelCount": 1000, "wilsonLowerBound": 0.10},
"vllm-mlu": {"modelCount": 800, "wilsonLowerBound": 0.35},
"vllm-customized": {"modelCount": 2, "wilsonLowerBound": 0.90},
}
}
}
}
ranked = manager.rank_frameworks(
task_type="text-generation",
target_gpu="gpu",
compatible_frameworks=["vllm", "vllm-customized", "vllm-mlu"],
)
self.assertEqual(["vllm-mlu", "vllm", "vllm-customized"], ranked)
def test_local_account_evidence_is_blended_without_overriding_sample_guards(self) -> None:
manager = MarketIntelligenceManager("unused.json", framework_min_samples=100)
manager.state = {
"frameworkStats": {
"text-generation": {
"gpu": {
"vllm": {"modelCount": 1000, "wilsonLowerBound": 0.30},
"vllm-mlu": {"modelCount": 1000, "wilsonLowerBound": 0.35},
}
}
}
}
manager.set_local_outcome_stats(
{
"combinationStats": {
"gpu|vllm|text-generation": {
"successCount": 20,
"failureCount": 0,
}
}
}
)
ranked = manager.rank_frameworks(
task_type="text-generation",
target_gpu="gpu",
compatible_frameworks=["vllm", "vllm-mlu"],
)
self.assertEqual("vllm", ranked[0])
metadata = manager.framework_metadata("text-generation", "gpu", "vllm")
self.assertEqual(20, metadata["frameworkLocalSamples"])
self.assertGreater(metadata["frameworkCombinedScore"], 0.35)
def test_candidate_uses_best_supported_public_framework(self) -> None:
manager = MarketIntelligenceManager("unused.json", framework_min_samples=100)
manager.state = {
"frameworkStats": {
"text-generation": {
"Cambricon_mlu-370-x4": {
"vllm": {
"modelCount": 1000,
"successCount": 100,
"successRate": 0.10,
"wilsonLowerBound": 0.08,
},
"vllm-mlu": {
"modelCount": 1000,
"successCount": 400,
"successRate": 0.40,
"wilsonLowerBound": 0.37,
},
}
}
}
}
candidate = choose_candidate_for_gpu(
model=HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=None,
pipeline_tag="text-generation",
),
inspection=ModelInspection(repo_id="owner/model", weight_files=["model.safetensors"]),
template_selector=TemplateSelector(),
task_types=["text-generation"],
target_gpu="Cambricon_mlu-370-x4",
market_intelligence=manager,
)
self.assertIsNotNone(candidate)
assert candidate is not None
self.assertEqual("vllm-mlu", candidate.framework)
self.assertAlmostEqual(0.37, candidate.score)
def test_new_framework_is_discovered_but_only_wins_on_qualified_success_score(self) -> None:
official_config = (
"framework: future-engine\n"
"sut_config:\n gpu_num: 1\n values: {}\n"
"ref_config:\n gpu_num: 1\n values: {}\n"
)
manager = MarketIntelligenceManager("unused.json", framework_min_samples=100)
manager.state = {
"frameworkStats": {
"text-generation": {
"Cambricon_mlu-370-x4": {
"vllm": {"modelCount": 1000, "wilsonLowerBound": 0.10},
"future-engine": {
"modelCount": 1000,
"successCount": 500,
"successRate": 0.50,
"wilsonLowerBound": 0.47,
"officialConfigValid": True,
"officialConfig": official_config,
},
}
}
}
}
candidate = choose_candidate_for_gpu(
model=HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=None,
pipeline_tag="text-generation",
),
inspection=ModelInspection(repo_id="owner/model", weight_files=["model.safetensors"]),
template_selector=TemplateSelector(),
task_types=["text-generation"],
target_gpu="Cambricon_mlu-370-x4",
market_intelligence=manager,
)
self.assertIsNotNone(candidate)
assert candidate is not None
self.assertEqual("future-engine", candidate.framework)
self.assertEqual(official_config, candidate.config_params)
self.assertIn("official_build_config_synced", candidate.warnings)
def test_tiny_new_framework_sample_does_not_displace_safe_legacy_framework(self) -> None:
manager = MarketIntelligenceManager("unused.json", framework_min_samples=100)
manager.state = {
"frameworkStats": {
"text-generation": {
"Cambricon_mlu-370-x4": {
"vllm": {"modelCount": 1000, "wilsonLowerBound": 0.10},
"future-engine": {
"modelCount": 2,
"wilsonLowerBound": 0.90,
"officialConfigValid": True,
"officialConfig": (
"framework: future-engine\n"
"sut_config:\n gpu_num: 1\n values: {}\n"
"ref_config:\n gpu_num: 1\n values: {}\n"
),
},
}
}
}
}
candidate = choose_candidate_for_gpu(
model=HFModelSummary(
repo_id="owner/model",
downloads=100,
last_modified=None,
pipeline_tag="text-generation",
),
inspection=ModelInspection(repo_id="owner/model", weight_files=["model.safetensors"]),
template_selector=TemplateSelector(),
task_types=["text-generation"],
target_gpu="Cambricon_mlu-370-x4",
market_intelligence=manager,
)
self.assertIsNotNone(candidate)
assert candidate is not None
self.assertEqual("vllm", candidate.framework)
if __name__ == "__main__":
unittest.main()