feat(us-026): smart model assignment via demand×coverage scoring
/v1/network/assign now scores models by (demand_rpm + 1) × (coverage_deficit + 0.01) so high-traffic, under-covered models are preferred when assigning new nodes. Response includes price_per_token: 0.0 (reserved for future pricing protocol). --memory MB flag added to node CLI to override autodetected VRAM budget for shard assignment without changing hardware detection for inference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1550,8 +1550,17 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
|
||||
if n.hf_repo not in repo_layers or n.num_layers > repo_layers[n.hf_repo]:
|
||||
repo_layers[n.hf_repo] = n.num_layers
|
||||
|
||||
# Pick the repo where the gap is largest (most work to do).
|
||||
# Smart scoring: demand_rpm × coverage_deficit
|
||||
# coverage_deficit = uncovered_layers / total_layers (0 = fully covered)
|
||||
# demand_rpm comes from the stats collector; defaults to 0.0 when no traffic yet.
|
||||
demand_rpms: dict[str, float] = {}
|
||||
if server.stats is not None:
|
||||
local_rpms = server.stats.get_local_rpms()
|
||||
for repo in repo_groups:
|
||||
demand_rpms[repo] = local_rpms.get(repo, {}).get("rpm_last_hour", 0.0)
|
||||
|
||||
best_repo = None
|
||||
best_score = -1.0
|
||||
best_gap_size = -1
|
||||
best_gap_start = 0
|
||||
best_num_layers = 0
|
||||
@@ -1569,8 +1578,13 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
|
||||
gap_start = max(gap_start, e + 1)
|
||||
else:
|
||||
break
|
||||
gap_size = max(0, (total - 1) - gap_start + 1) # layers remaining uncovered
|
||||
if gap_size > best_gap_size:
|
||||
gap_size = max(0, (total - 1) - gap_start + 1)
|
||||
coverage_deficit = gap_size / max(total, 1)
|
||||
demand = demand_rpms.get(repo, 0.0)
|
||||
# +1.0 floor on demand so models with no recorded traffic still compete by coverage.
|
||||
score = (demand + 1.0) * (coverage_deficit + 0.01)
|
||||
if score > best_score or (score == best_score and gap_size > best_gap_size):
|
||||
best_score = score
|
||||
best_gap_size = gap_size
|
||||
best_gap_start = gap_start
|
||||
best_repo = repo
|
||||
@@ -1578,8 +1592,8 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
gap_found = best_gap_size > 0
|
||||
if not gap_found:
|
||||
# All shards are covered — still assign to the model with most nodes for redundancy.
|
||||
best_repo = max(repo_groups, key=lambda r: len(repo_groups[r]))
|
||||
# All shards covered — assign to highest-demand model for redundancy.
|
||||
best_repo = max(repo_groups, key=lambda r: demand_rpms.get(r, 0.0))
|
||||
best_gap_start = 0
|
||||
best_num_layers = repo_layers[best_repo]
|
||||
|
||||
@@ -1599,6 +1613,7 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
|
||||
"shard_end": shard_end,
|
||||
"num_layers": total_l,
|
||||
"gap_found": gap_found,
|
||||
"price_per_token": 0.0,
|
||||
})
|
||||
|
||||
def _handle_route(self, parsed: urllib.parse.ParseResult):
|
||||
|
||||
Reference in New Issue
Block a user