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.
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
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, "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, "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-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",
|
||||
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
|
||||
"icon": "Cpu",
|
||||
"version": "1.5.18",
|
||||
"version": "1.5.19",
|
||||
"tab": {"path": "/ollama-manager", "position": "after:models"},
|
||||
"entry": "dist/index.js",
|
||||
"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)]
|
||||
|
||||
|
||||
def _architecture(name: str, family: str, details: dict[str, Any]) -> tuple[str, bool]:
|
||||
text = f"{name} {family} {details.get('parent_model', '')}".lower()
|
||||
moe = any(token in text for token in ("moe", "mixture", "_h_", "a3b"))
|
||||
def _page_description(html: str) -> str:
|
||||
patterns = [
|
||||
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"
|
||||
if 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 "")
|
||||
capabilities = _infer_capabilities(name, family, details, raw.get("capabilities"))
|
||||
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
|
||||
ram_gb, ram_basis = _ram_estimate(size_bytes, details.get("parameter_size"), details.get("quantization_level"), context_length)
|
||||
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",
|
||||
"architecture": architecture,
|
||||
"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",
|
||||
"format": details.get("format") or "unknown",
|
||||
"context_length": context_length,
|
||||
@@ -1055,8 +1106,22 @@ def _fetch_library_families() -> list[str]:
|
||||
|
||||
def _fetch_family_variants(family: str) -> list[dict[str, Any]]:
|
||||
try:
|
||||
html = _text_request(f"{REMOTE_OLLAMA}/library/{family}/tags", timeout=30)
|
||||
return _parse_variant_page(html, family)
|
||||
tags_html = _text_request(f"{REMOTE_OLLAMA}/library/{family}/tags", timeout=30)
|
||||
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):
|
||||
return []
|
||||
|
||||
@@ -1764,6 +1829,11 @@ def status() -> dict[str, Any]:
|
||||
|
||||
def catalog_view(raw: dict[str, Any], source: str) -> dict[str, Any]:
|
||||
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["installed"] = name in installed_names
|
||||
view["loaded"] = name in loaded
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
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.
|
||||
auto_install_dependencies: true
|
||||
python_dependencies:
|
||||
|
||||
Reference in New Issue
Block a user