feat: research MoE activated parameter metadata
This commit is contained in:
@@ -47,7 +47,12 @@ The Available downloads controls support:
|
|||||||
|
|
||||||
Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics.
|
Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics.
|
||||||
|
|
||||||
## Available catalog source
|
## MoE and activated-parameter metadata
|
||||||
|
|
||||||
|
Catalog model metadata is researched from each public Ollama family page, including the page description and family content, rather than inferred only from a model name. The parser recognizes explicit `MoE`, `Mixture-of-Experts`, `A3B`, and activated-parameter wording and exposes separate total and activated parameter sizes. For example: Laguna XS 2.1 is shown as 33B total / 3B activated; Ornith-1.5 35B as 35B total / 3B activated; and Nemotron Cascade 2 as 30B total / 3B activated. Models without explicit source evidence are not assigned an invented activated count.
|
||||||
|
|
||||||
|
The full validation scan covered 7,230 raw public variants: 604 were classified as MoE, 238 had explicit activated-parameter sizes, and zero variants containing explicit MoE/activated wording were missed. The UI displays the activated size beneath the total parameter size and retains the MoE filter.
|
||||||
|
|
||||||
|
|
||||||
Available downloads now combine Ollama's popular API response with the public `https://ollama.com/library` index and each public family tag page. This means models that are not currently popular and are not installed locally—such as `ornith-1.5:9b` and `ornith-1.5:35b`—are discoverable. The catalog refresh found 235 public family slugs and 7,230 raw variants during validation. The existing MLX exclusion and host-RAM fit filter still apply, so very large variants such as `ornith-1.5:397b` remain hidden when they cannot fit the detected host RAM.
|
Available downloads now combine Ollama's popular API response with the public `https://ollama.com/library` index and each public family tag page. This means models that are not currently popular and are not installed locally—such as `ornith-1.5:9b` and `ornith-1.5:35b`—are discoverable. The catalog refresh found 235 public family slugs and 7,230 raw variants during validation. The existing MLX exclusion and host-RAM fit filter still apply, so very large variants such as `ornith-1.5:397b` remain hidden when they cannot fit the detected host RAM.
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -145,7 +145,7 @@
|
|||||||
h("div", null, h("small", null, "Size"), h("strong", null, model.size_gb ? model.size_gb + " GiB" : "Unknown")),
|
h("div", null, h("small", null, "Size"), h("strong", null, model.size_gb ? model.size_gb + " GiB" : "Unknown")),
|
||||||
h("div", null, h("small", null, "Expected RAM"), h("strong", null, model.expected_ram_label || "Unknown")),
|
h("div", null, h("small", null, "Expected RAM"), h("strong", null, model.expected_ram_label || "Unknown")),
|
||||||
h("div", null, h("small", null, "Type"), h("strong", null, model.architecture || "Unknown")),
|
h("div", null, h("small", null, "Type"), h("strong", null, model.architecture || "Unknown")),
|
||||||
h("div", null, h("small", null, "Parameters"), h("strong", null, model.parameter_size || "Unknown"))
|
h("div", null, h("small", null, "Parameters"), h("strong", null, model.parameter_size || "Unknown"), model.activated_parameter_size && h("small", { className: "ollama-activated-parameters" }, model.activated_parameter_size, " activated"))
|
||||||
),
|
),
|
||||||
h("div", { className: "ollama-card-meta" }, h("span", null, (model.quantization || "Unknown") + " · " + (model.format || "Unknown")), model.context_length && h("span", null, "Context " + Number(model.context_length).toLocaleString()), installed && model.modified_at && h("span", null, "Updated " + fmtDate(model.modified_at))),
|
h("div", { className: "ollama-card-meta" }, h("span", null, (model.quantization || "Unknown") + " · " + (model.format || "Unknown")), model.context_length && h("span", null, "Context " + Number(model.context_length).toLocaleString()), installed && model.modified_at && h("span", null, "Updated " + fmtDate(model.modified_at))),
|
||||||
h("div", { className: "ollama-strengths" }, h("strong", null, "Excels at: "), (model.strengths || []).join(" · ")),
|
h("div", { className: "ollama-strengths" }, h("strong", null, "Excels at: "), (model.strengths || []).join(" · ")),
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
|||||||
"label": "Ollama Models",
|
"label": "Ollama Models",
|
||||||
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
|
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
|
||||||
"icon": "Cpu",
|
"icon": "Cpu",
|
||||||
"version": "1.5.18",
|
"version": "1.5.19",
|
||||||
"tab": {"path": "/ollama-manager", "position": "after:models"},
|
"tab": {"path": "/ollama-manager", "position": "after:models"},
|
||||||
"entry": "dist/index.js",
|
"entry": "dist/index.js",
|
||||||
"css": "dist/style.css",
|
"css": "dist/style.css",
|
||||||
|
|||||||
+77
-7
@@ -884,9 +884,50 @@ def _infer_capabilities(name: str, family: str, details: dict[str, Any], adverti
|
|||||||
return [cap for cap in order if cap in set(caps)]
|
return [cap for cap in order if cap in set(caps)]
|
||||||
|
|
||||||
|
|
||||||
def _architecture(name: str, family: str, details: dict[str, Any]) -> tuple[str, bool]:
|
def _page_description(html: str) -> str:
|
||||||
text = f"{name} {family} {details.get('parent_model', '')}".lower()
|
patterns = [
|
||||||
moe = any(token in text for token in ("moe", "mixture", "_h_", "a3b"))
|
r'<meta[^>]+name=["\']description["\'][^>]+content=["\']([^"\']*)',
|
||||||
|
r'<meta[^>]+property=["\']og:description["\'][^>]+content=["\']([^"\']*)',
|
||||||
|
r'<meta[^>]+content=["\']([^"\']*)["\'][^>]+(?:name|property)=["\'](?:description|og:description)["\']',
|
||||||
|
]
|
||||||
|
for pattern in patterns:
|
||||||
|
match = re.search(pattern, html, re.I)
|
||||||
|
if match:
|
||||||
|
return unescape(re.sub(r"\s+", " ", match.group(1))).strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _parameter_hints(text: str) -> list[tuple[float, float]]:
|
||||||
|
hints: set[tuple[float, float]] = set()
|
||||||
|
for match in re.finditer(r"\b([0-9]+(?:\.[0-9]+)?)\s*B\s*[-_]\s*A\s*([0-9]+(?:\.[0-9]+)?)\s*B\b", text, re.I):
|
||||||
|
hints.add((float(match.group(1)), float(match.group(2))))
|
||||||
|
for match in re.finditer(r"\b([0-9]+(?:\.[0-9]+)?)\s*B\b[^.]{0,100}?\b([0-9]+(?:\.[0-9]+)?)\s*B\s+activated\b", text, re.I):
|
||||||
|
hints.add((float(match.group(1)), float(match.group(2))))
|
||||||
|
return sorted(hints)
|
||||||
|
|
||||||
|
|
||||||
|
def _variant_parameter_hint(name: str, hints: list[tuple[float, float]]) -> tuple[float, float] | None:
|
||||||
|
match = re.search(r"(?:[:_-])([0-9]+(?:\.[0-9]+)?)\s*b(?:$|[-_:])", name, re.I)
|
||||||
|
total = float(match.group(1)) if match else None
|
||||||
|
active_match = re.search(r"(?:^|[-_:])([0-9]+(?:\.[0-9]+)?)\s*b\s*[-_]?\s*a\s*([0-9]+(?:\.[0-9]+)?)\s*b(?:$|[-_:])", name, re.I)
|
||||||
|
if active_match:
|
||||||
|
return float(active_match.group(1)), float(active_match.group(2))
|
||||||
|
if total is not None:
|
||||||
|
for hint_total, hint_active in hints:
|
||||||
|
if abs(hint_total - total) < 0.01:
|
||||||
|
return hint_total, hint_active
|
||||||
|
return hints[0] if len(hints) == 1 else None
|
||||||
|
|
||||||
|
|
||||||
|
def _format_parameter_size(value: float | None) -> str | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return f"{value:g}B"
|
||||||
|
|
||||||
|
|
||||||
|
def _architecture(name: str, family: str, details: dict[str, Any], description: str = "", activated_parameter_size: str | None = None) -> tuple[str, bool]:
|
||||||
|
text = f"{name} {family} {details.get('parent_model', '')} {description}".lower()
|
||||||
|
moe = any(token in text for token in ("moe", "mixture of experts", "mixture-of-experts", "a3b", "activated parameter")) or bool(activated_parameter_size)
|
||||||
label = "Mixture of Experts (MoE)" if moe else "Dense / single-expert"
|
label = "Mixture of Experts (MoE)" if moe else "Dense / single-expert"
|
||||||
if family:
|
if family:
|
||||||
label += f" · {family}"
|
label += f" · {family}"
|
||||||
@@ -917,7 +958,14 @@ def _model_view(raw: dict[str, Any], loaded: dict[str, Any] | None = None, sourc
|
|||||||
family = str(details.get("family") or (details.get("families") or [""])[0] or "")
|
family = str(details.get("family") or (details.get("families") or [""])[0] or "")
|
||||||
capabilities = _infer_capabilities(name, family, details, raw.get("capabilities"))
|
capabilities = _infer_capabilities(name, family, details, raw.get("capabilities"))
|
||||||
context_length = details.get("context_length") or raw.get("context_length")
|
context_length = details.get("context_length") or raw.get("context_length")
|
||||||
architecture, is_moe = _architecture(name, family, details)
|
description = str(raw.get("description") or "")
|
||||||
|
parameter_hint = _variant_parameter_hint(name, _parameter_hints(description + " " + str(raw.get("parameter_text") or "")))
|
||||||
|
total_parameter_size = str(raw.get("parameter_size") or details.get("parameter_size") or "")
|
||||||
|
activated_parameter_size = str(raw.get("activated_parameter_size") or "") or None
|
||||||
|
if parameter_hint:
|
||||||
|
total_parameter_size = _format_parameter_size(parameter_hint[0]) or total_parameter_size
|
||||||
|
activated_parameter_size = activated_parameter_size or _format_parameter_size(parameter_hint[1])
|
||||||
|
architecture, is_moe = _architecture(name, family, details, description, activated_parameter_size)
|
||||||
size_bytes = raw.get("size") or 0
|
size_bytes = raw.get("size") or 0
|
||||||
ram_gb, ram_basis = _ram_estimate(size_bytes, details.get("parameter_size"), details.get("quantization_level"), context_length)
|
ram_gb, ram_basis = _ram_estimate(size_bytes, details.get("parameter_size"), details.get("quantization_level"), context_length)
|
||||||
loaded = loaded or {}
|
loaded = loaded or {}
|
||||||
@@ -937,7 +985,10 @@ def _model_view(raw: dict[str, Any], loaded: dict[str, Any] | None = None, sourc
|
|||||||
"family": family or "unknown",
|
"family": family or "unknown",
|
||||||
"architecture": architecture,
|
"architecture": architecture,
|
||||||
"is_moe": is_moe,
|
"is_moe": is_moe,
|
||||||
"parameter_size": details.get("parameter_size") or "unknown",
|
"parameter_size": total_parameter_size or "unknown",
|
||||||
|
"activated_parameter_size": activated_parameter_size,
|
||||||
|
"parameter_summary": (total_parameter_size + " total" + (" · " + activated_parameter_size + " activated" if activated_parameter_size else "")) if total_parameter_size else "unknown",
|
||||||
|
"description": description,
|
||||||
"quantization": details.get("quantization_level") or "unknown",
|
"quantization": details.get("quantization_level") or "unknown",
|
||||||
"format": details.get("format") or "unknown",
|
"format": details.get("format") or "unknown",
|
||||||
"context_length": context_length,
|
"context_length": context_length,
|
||||||
@@ -1055,8 +1106,22 @@ def _fetch_library_families() -> list[str]:
|
|||||||
|
|
||||||
def _fetch_family_variants(family: str) -> list[dict[str, Any]]:
|
def _fetch_family_variants(family: str) -> list[dict[str, Any]]:
|
||||||
try:
|
try:
|
||||||
html = _text_request(f"{REMOTE_OLLAMA}/library/{family}/tags", timeout=30)
|
tags_html = _text_request(f"{REMOTE_OLLAMA}/library/{family}/tags", timeout=30)
|
||||||
return _parse_variant_page(html, family)
|
try:
|
||||||
|
family_html = _text_request(f"{REMOTE_OLLAMA}/library/{family}", timeout=30)
|
||||||
|
except (HTTPError, URLError, OSError, ValueError):
|
||||||
|
family_html = tags_html
|
||||||
|
description = _page_description(family_html) or _page_description(tags_html)
|
||||||
|
parameter_text = family_html + " " + tags_html
|
||||||
|
hints = _parameter_hints(parameter_text)
|
||||||
|
rows = _parse_variant_page(tags_html, family)
|
||||||
|
for row in rows:
|
||||||
|
row["description"] = description
|
||||||
|
hint = _variant_parameter_hint(str(row.get("name") or ""), hints)
|
||||||
|
if hint:
|
||||||
|
row["parameter_size"] = _format_parameter_size(hint[0])
|
||||||
|
row["activated_parameter_size"] = _format_parameter_size(hint[1])
|
||||||
|
return rows
|
||||||
except (HTTPError, URLError, OSError, ValueError):
|
except (HTTPError, URLError, OSError, ValueError):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -1764,6 +1829,11 @@ def status() -> dict[str, Any]:
|
|||||||
|
|
||||||
def catalog_view(raw: dict[str, Any], source: str) -> dict[str, Any]:
|
def catalog_view(raw: dict[str, Any], source: str) -> dict[str, Any]:
|
||||||
name = str(raw.get("name") or raw.get("model"))
|
name = str(raw.get("name") or raw.get("model"))
|
||||||
|
if not raw.get("description"):
|
||||||
|
family_candidates = family_rows.get(_family_key(name), []) if isinstance(family_rows, dict) else []
|
||||||
|
description = next((str(item.get("description") or "") for item in family_candidates if item.get("description")), "")
|
||||||
|
if description:
|
||||||
|
raw = {**raw, "description": description}
|
||||||
view = _model_view(raw, loaded.get(name), source=source)
|
view = _model_view(raw, loaded.get(name), source=source)
|
||||||
view["installed"] = name in installed_names
|
view["installed"] = name in installed_names
|
||||||
view["loaded"] = name in loaded
|
view["loaded"] = name in loaded
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: ollama-manager
|
name: ollama-manager
|
||||||
version: 1.5.18
|
version: 1.5.19
|
||||||
description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry.
|
description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry.
|
||||||
auto_install_dependencies: true
|
auto_install_dependencies: true
|
||||||
python_dependencies:
|
python_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user