feat: add queue-aware adaptive scheduling
This commit is contained in:
@@ -18,6 +18,15 @@ from history_stats import (
|
||||
load_ledger,
|
||||
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 (
|
||||
DEFAULT_CAPACITY_STATE_PATH,
|
||||
ModelHubAPIError,
|
||||
@@ -30,7 +39,7 @@ from models import CandidateModel, HFModelSummary, ModelInspection
|
||||
from outcome_tracker import DEFAULT_OUTCOMES_PATH, OutcomeTracker
|
||||
from submission_claims import DEFAULT_CLAIMS_PATH, SubmissionClaimStore, candidate_key, diversify_candidates
|
||||
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
|
||||
|
||||
|
||||
@@ -61,6 +70,11 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
action="store_true",
|
||||
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(
|
||||
"--gpu-strategy-refresh-submissions",
|
||||
type=int,
|
||||
@@ -107,6 +121,41 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
default=os.getenv("MODELHUB_SUBMISSION_EXCLUSIONS_PATH", str(DEFAULT_SUBMISSION_EXCLUSIONS_PATH)),
|
||||
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(
|
||||
"--gpu-strategy-state-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,
|
||||
task_types: list[str],
|
||||
target_gpu: str,
|
||||
market_intelligence: MarketIntelligenceManager | None = None,
|
||||
) -> CandidateModel | None:
|
||||
for task_type in task_types:
|
||||
supported_frameworks = template_selector.supported_frameworks_for_auto(task_type, target_gpu)
|
||||
if not supported_frameworks:
|
||||
continue
|
||||
compatible_frameworks: list[str] = []
|
||||
try:
|
||||
framework = choose_framework_for_task(task_type, target_gpu, supported_frameworks, inspection)
|
||||
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,
|
||||
)
|
||||
if supported_frameworks:
|
||||
compatible_frameworks = compatible_frameworks_for_task(
|
||||
task_type,
|
||||
target_gpu,
|
||||
supported_frameworks,
|
||||
inspection,
|
||||
)
|
||||
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
|
||||
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.template_id,
|
||||
config_params=config_params,
|
||||
downloads=model.downloads,
|
||||
last_modified=model.last_modified,
|
||||
gguf_filename=inspection.selected_gguf,
|
||||
score=0.0,
|
||||
warnings=[],
|
||||
)
|
||||
if market_intelligence is not None:
|
||||
compatible_frameworks = market_intelligence.rank_frameworks(
|
||||
task_type=task_type,
|
||||
target_gpu=target_gpu,
|
||||
compatible_frameworks=compatible_frameworks,
|
||||
)
|
||||
|
||||
for framework in compatible_frameworks:
|
||||
config_params = (
|
||||
market_intelligence.official_config(
|
||||
task_type=task_type,
|
||||
target_gpu=target_gpu,
|
||||
framework=framework,
|
||||
gguf_filename=inspection.selected_gguf,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@@ -284,6 +386,7 @@ def process_model_for_candidates(
|
||||
allowed_task_types: list[str],
|
||||
outcome_tracker: OutcomeTracker | 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]]]:
|
||||
specs = [spec for spec in task_specs_for_model(model) if spec.task_type in allowed_task_types]
|
||||
if not specs:
|
||||
@@ -345,11 +448,16 @@ def process_model_for_candidates(
|
||||
template_selector=template_selector,
|
||||
task_types=task_types,
|
||||
target_gpu=target_gpu,
|
||||
market_intelligence=market_intelligence,
|
||||
)
|
||||
if best is None:
|
||||
skipped.append({"repoId": model.repo_id, "targetGpu": target_gpu, "reason": "no_compatible_auto_template_or_framework"})
|
||||
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
|
||||
|
||||
@@ -423,6 +531,7 @@ def collect_candidates_from_models(
|
||||
outcome_tracker: OutcomeTracker | None,
|
||||
submission_exclusion_store: SubmissionExclusionStore | None,
|
||||
read_concurrency: int,
|
||||
market_intelligence: MarketIntelligenceManager | None = None,
|
||||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], int]:
|
||||
candidates: list[dict[str, Any]] = []
|
||||
skipped: list[dict[str, Any]] = []
|
||||
@@ -451,6 +560,7 @@ def collect_candidates_from_models(
|
||||
allowed_task_types=selected_task_types,
|
||||
outcome_tracker=outcome_tracker,
|
||||
submission_exclusion_store=submission_exclusion_store,
|
||||
market_intelligence=market_intelligence,
|
||||
): index
|
||||
for index, model in enumerate(chunk)
|
||||
}
|
||||
@@ -624,6 +734,8 @@ def run_submission(
|
||||
)
|
||||
strategy_manager: GPUStrategyManager | None = None
|
||||
strategy_summary: dict[str, Any] = {"enabled": False}
|
||||
market_intelligence: MarketIntelligenceManager | None = None
|
||||
market_summary: dict[str, Any] = {"enabled": False}
|
||||
strategy_enabled = (
|
||||
not bool(getattr(args, "disable_gpu_strategy", False))
|
||||
and not bool(getattr(args, "gpu", None) or getattr(args, "gpus", None))
|
||||
@@ -707,15 +819,19 @@ def run_submission(
|
||||
"historyArchivePath": str(history_archive_path),
|
||||
"historyArchiveRecordCount": len(archived_history),
|
||||
"gpuStrategy": strategy_summary,
|
||||
"marketIntelligence": market_summary,
|
||||
"scanLimit": 0,
|
||||
"candidateGoal": 0,
|
||||
"scanStages": [],
|
||||
"scannedModels": 0,
|
||||
"candidateCount": 0,
|
||||
"candidateFrameworkCounts": {},
|
||||
"targetSubmitCount": 0,
|
||||
"maxSubmitAttempts": 0,
|
||||
"plannedSubmitCount": 0,
|
||||
"submittedCount": 0,
|
||||
"submittedGpuCounts": {},
|
||||
"submittedFrameworkCounts": {},
|
||||
"duplicateCount": 0,
|
||||
"modelGpuUniquenessRejectedCount": 0,
|
||||
"skippedCount": 0,
|
||||
@@ -728,12 +844,63 @@ def run_submission(
|
||||
"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:
|
||||
strategy_manager = GPUStrategyManager(
|
||||
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)),
|
||||
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)),
|
||||
market_intelligence=market_intelligence,
|
||||
)
|
||||
strategy_manager.prepare(modelhub_client, supported_gpus=target_gpus, now=now)
|
||||
strategy_summary = strategy_manager.summary()
|
||||
@@ -824,6 +991,7 @@ def run_submission(
|
||||
outcome_tracker=outcome_tracker,
|
||||
submission_exclusion_store=submission_exclusion_store,
|
||||
read_concurrency=max(1, args.read_concurrency),
|
||||
market_intelligence=market_intelligence,
|
||||
)
|
||||
candidates.extend(stage_candidates)
|
||||
skipped.extend(stage_skipped)
|
||||
@@ -1027,6 +1195,9 @@ def run_submission(
|
||||
outcome_tracker.save()
|
||||
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)
|
||||
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 = {
|
||||
"generatedAt": now.isoformat(),
|
||||
@@ -1044,11 +1215,13 @@ def run_submission(
|
||||
"historyArchivePath": str(history_archive_path),
|
||||
"historyArchiveRecordCount": len(archived_history),
|
||||
"gpuStrategy": strategy_summary,
|
||||
"marketIntelligence": market_summary,
|
||||
"scanLimit": scan_limit,
|
||||
"candidateGoal": candidate_goal,
|
||||
"scanStages": scan_stages,
|
||||
"scannedModels": scanned_model_count,
|
||||
"candidateCount": len(candidates),
|
||||
"candidateFrameworkCounts": dict(candidate_framework_counts.most_common()),
|
||||
"targetSubmitCount": target_submit_count,
|
||||
"maxSubmitAttempts": 0 if args.dry_run else min(
|
||||
len(diversified_candidates),
|
||||
@@ -1058,6 +1231,8 @@ def run_submission(
|
||||
"duplicateCount": len(duplicate_candidates),
|
||||
"modelGpuUniquenessRejectedCount": len(uniqueness_rejected_candidates),
|
||||
"submittedCount": len(submitted),
|
||||
"submittedGpuCounts": dict(submitted_gpu_counts.most_common()),
|
||||
"submittedFrameworkCounts": dict(submitted_framework_counts.most_common()),
|
||||
"skippedCount": len(skipped),
|
||||
"skipReasonCounts": dict(skip_reason_counts.most_common()),
|
||||
"failedCount": len(failed),
|
||||
|
||||
Reference in New Issue
Block a user