mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-31 03:12:16 +00:00
Merge pull request #130 from jbergant/fix/qwen35-model-catalog-mlx-129
fix: correct Qwen3.5 model sizes and MLX repos in catalog
This commit is contained in:
@@ -35,17 +35,13 @@ def _check_python_version() -> CheckResult:
|
||||
version_str = f"{ver.major}.{ver.minor}.{ver.micro}"
|
||||
if (ver.major, ver.minor) >= (3, 10):
|
||||
return CheckResult("Python version", "ok", version_str)
|
||||
return CheckResult(
|
||||
"Python version", "fail", f"{version_str} (requires >= 3.10)"
|
||||
)
|
||||
return CheckResult("Python version", "fail", f"{version_str} (requires >= 3.10)")
|
||||
|
||||
|
||||
def _check_config_exists() -> CheckResult:
|
||||
"""Check that the config file exists."""
|
||||
if DEFAULT_CONFIG_PATH.exists():
|
||||
return CheckResult(
|
||||
"Config file", "ok", str(DEFAULT_CONFIG_PATH)
|
||||
)
|
||||
return CheckResult("Config file", "ok", str(DEFAULT_CONFIG_PATH))
|
||||
return CheckResult(
|
||||
"Config file",
|
||||
"warn",
|
||||
@@ -57,16 +53,12 @@ def _check_config_exists() -> CheckResult:
|
||||
def _check_config_parses() -> CheckResult:
|
||||
"""Check that the config file parses successfully."""
|
||||
if not DEFAULT_CONFIG_PATH.exists():
|
||||
return CheckResult(
|
||||
"Config parsing", "warn", "Skipped (no config file)"
|
||||
)
|
||||
return CheckResult("Config parsing", "warn", "Skipped (no config file)")
|
||||
try:
|
||||
load_config()
|
||||
return CheckResult("Config parsing", "ok", "Config loaded successfully")
|
||||
except Exception as exc:
|
||||
return CheckResult(
|
||||
"Config parsing", "fail", f"Parse error: {exc}"
|
||||
)
|
||||
return CheckResult("Config parsing", "fail", f"Parse error: {exc}")
|
||||
|
||||
|
||||
def _ensure_engines_imported() -> None:
|
||||
@@ -102,22 +94,16 @@ def _check_engines() -> List[CheckResult]:
|
||||
try:
|
||||
engine = _discovery._make_engine(key, config)
|
||||
if engine.health():
|
||||
results.append(
|
||||
CheckResult(f"Engine: {key}", "ok", "Reachable")
|
||||
)
|
||||
results.append(CheckResult(f"Engine: {key}", "ok", "Reachable"))
|
||||
else:
|
||||
results.append(
|
||||
CheckResult(f"Engine: {key}", "warn", "Unreachable")
|
||||
)
|
||||
results.append(CheckResult(f"Engine: {key}", "warn", "Unreachable"))
|
||||
except Exception as exc:
|
||||
results.append(
|
||||
CheckResult(f"Engine: {key}", "warn", f"Unreachable ({exc})")
|
||||
)
|
||||
|
||||
if not results:
|
||||
results.append(
|
||||
CheckResult("Engines", "warn", "No engines registered")
|
||||
)
|
||||
results.append(CheckResult("Engines", "warn", "No engines registered"))
|
||||
|
||||
return results
|
||||
|
||||
@@ -154,7 +140,7 @@ def _check_models() -> List[CheckResult]:
|
||||
f"Models: {key}",
|
||||
"warn",
|
||||
"No models available",
|
||||
details="Pull a model (e.g. `ollama pull qwen3.5:3b`).",
|
||||
details="Pull a model (e.g. `ollama pull qwen3.5:2b`).",
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
@@ -168,9 +154,7 @@ def _check_default_model() -> CheckResult:
|
||||
try:
|
||||
config = load_config()
|
||||
except Exception:
|
||||
return CheckResult(
|
||||
"Default model", "warn", "Skipped (config unavailable)"
|
||||
)
|
||||
return CheckResult("Default model", "warn", "Skipped (config unavailable)")
|
||||
|
||||
default_model = config.intelligence.default_model
|
||||
if not default_model:
|
||||
@@ -221,9 +205,7 @@ def _check_optional_deps() -> List[CheckResult]:
|
||||
for pkg, install_hint, description in optional_packages:
|
||||
try:
|
||||
__import__(pkg)
|
||||
results.append(
|
||||
CheckResult(f"Optional: {description}", "ok", "Installed")
|
||||
)
|
||||
results.append(CheckResult(f"Optional: {description}", "ok", "Installed"))
|
||||
except Exception:
|
||||
results.append(
|
||||
CheckResult(
|
||||
@@ -266,8 +248,7 @@ def _check_nodejs() -> CheckResult:
|
||||
"warn",
|
||||
f"{version_str} (requires >= v22)",
|
||||
details=(
|
||||
"Upgrade Node.js for ClaudeCodeAgent and WhatsApp "
|
||||
"Baileys support."
|
||||
"Upgrade Node.js for ClaudeCodeAgent and WhatsApp Baileys support."
|
||||
),
|
||||
)
|
||||
except Exception as exc:
|
||||
@@ -335,7 +316,5 @@ def doctor(as_json: bool) -> None:
|
||||
warn_count = sum(1 for c in checks if c.status == "warn")
|
||||
fail_count = sum(1 for c in checks if c.status == "fail")
|
||||
console.print()
|
||||
console.print(
|
||||
f" {ok_count} passed, {warn_count} warnings, {fail_count} failures"
|
||||
)
|
||||
console.print(f" {ok_count} passed, {warn_count} warnings, {fail_count} failures")
|
||||
console.print()
|
||||
|
||||
@@ -40,6 +40,6 @@ def hint_no_model(model_name: Optional[str] = None) -> str:
|
||||
)
|
||||
return (
|
||||
"[yellow]Hint:[/yellow] No models available.\n"
|
||||
" Pull a model first: [bold]ollama pull qwen3.5:3b[/bold]\n"
|
||||
" Pull a model first: [bold]ollama pull qwen3.5:2b[/bold]\n"
|
||||
" Run [bold]jarvis model list[/bold] to see available models."
|
||||
)
|
||||
|
||||
@@ -64,7 +64,7 @@ def _detect_running_engines() -> list[str]:
|
||||
|
||||
def _next_steps_text(engine: str, model: str = "") -> str:
|
||||
"""Return engine-specific next-steps guidance after init."""
|
||||
pull_model = model or "qwen3.5:3b"
|
||||
pull_model = model or "qwen3.5:2b"
|
||||
steps: dict[str, str] = {
|
||||
"ollama": (
|
||||
"Next steps:\n"
|
||||
|
||||
@@ -106,8 +106,7 @@ def quickstart(force: bool) -> None:
|
||||
console.print("[bold cyan][2/5][/bold cyan] Writing config...")
|
||||
if DEFAULT_CONFIG_PATH.exists() and not force:
|
||||
console.print(
|
||||
f" [dim]Config already exists at"
|
||||
f" {DEFAULT_CONFIG_PATH} (skip)[/dim]"
|
||||
f" [dim]Config already exists at {DEFAULT_CONFIG_PATH} (skip)[/dim]"
|
||||
)
|
||||
else:
|
||||
toml_content = generate_default_toml(hw)
|
||||
@@ -144,7 +143,7 @@ def quickstart(force: bool) -> None:
|
||||
if not _check_model_available(active_engine):
|
||||
console.print(" [yellow]No models found.[/yellow]")
|
||||
console.print(
|
||||
" Pull a model first (e.g. [bold]ollama pull qwen3.5:3b[/bold])."
|
||||
" Pull a model first (e.g. [bold]ollama pull qwen3.5:2b[/bold])."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
console.print(" [green]Models available.[/green]")
|
||||
@@ -157,6 +156,5 @@ def quickstart(force: bool) -> None:
|
||||
|
||||
console.print()
|
||||
console.print(
|
||||
'[bold green]Setup complete![/bold green]'
|
||||
' Try: [bold]jarvis ask "Hello"[/bold]'
|
||||
'[bold green]Setup complete![/bold green] Try: [bold]jarvis ask "Hello"[/bold]'
|
||||
)
|
||||
|
||||
@@ -32,9 +32,9 @@ name = "mlx-community/Qwen2.5-7B-4bit"
|
||||
engine = "mlx"
|
||||
param_count_b = 7.0
|
||||
|
||||
# Ollama GGUF — pull with: ollama pull qwen3.5:3b
|
||||
# Ollama GGUF — pull with: ollama pull qwen3.5:2b
|
||||
[[models]]
|
||||
name = "qwen3.5:3b"
|
||||
name = "qwen3.5:2b"
|
||||
engine = "ollama"
|
||||
|
||||
# ── Benchmarks ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -40,48 +40,62 @@ BUILTIN_MODELS: List[ModelSpec] = [
|
||||
# Local models — Qwen3.5 (MoE)
|
||||
# -----------------------------------------------------------------------
|
||||
ModelSpec(
|
||||
model_id="qwen3.5:3b",
|
||||
name="Qwen3.5 3B",
|
||||
parameter_count_b=3.0,
|
||||
active_parameter_count_b=0.6,
|
||||
model_id="qwen3.5:0.8b",
|
||||
name="Qwen3.5 0.8B",
|
||||
parameter_count_b=0.8,
|
||||
active_parameter_count_b=0.15,
|
||||
context_length=131072,
|
||||
supported_engines=("ollama", "vllm", "llamacpp", "sglang", "mlx"),
|
||||
provider="alibaba",
|
||||
metadata={
|
||||
"architecture": "moe",
|
||||
"hf_repo": "Qwen/Qwen3.5-3B",
|
||||
"gguf_file": "qwen3.5-3b-q4_k_m.gguf",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-3B-4bit",
|
||||
"hf_repo": "Qwen/Qwen3.5-0.8B",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-0.8B-OptiQ-4bit",
|
||||
},
|
||||
),
|
||||
ModelSpec(
|
||||
model_id="qwen3.5:8b",
|
||||
name="Qwen3.5 8B",
|
||||
parameter_count_b=8.0,
|
||||
active_parameter_count_b=1.0,
|
||||
model_id="qwen3.5:2b",
|
||||
name="Qwen3.5 2B",
|
||||
parameter_count_b=2.0,
|
||||
active_parameter_count_b=0.4,
|
||||
context_length=131072,
|
||||
supported_engines=("ollama", "vllm", "llamacpp", "sglang", "mlx"),
|
||||
provider="alibaba",
|
||||
metadata={
|
||||
"architecture": "moe",
|
||||
"hf_repo": "Qwen/Qwen3.5-8B",
|
||||
"gguf_file": "qwen3.5-8b-q4_k_m.gguf",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-8B-4bit",
|
||||
"hf_repo": "Qwen/Qwen3.5-2B",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-2B-OptiQ-4bit",
|
||||
},
|
||||
),
|
||||
ModelSpec(
|
||||
model_id="qwen3.5:14b",
|
||||
name="Qwen3.5 14B",
|
||||
parameter_count_b=14.0,
|
||||
active_parameter_count_b=2.0,
|
||||
model_id="qwen3.5:9b",
|
||||
name="Qwen3.5 9B",
|
||||
parameter_count_b=9.0,
|
||||
active_parameter_count_b=1.5,
|
||||
context_length=131072,
|
||||
supported_engines=("ollama", "vllm", "llamacpp", "sglang", "mlx"),
|
||||
provider="alibaba",
|
||||
metadata={
|
||||
"architecture": "moe",
|
||||
"hf_repo": "Qwen/Qwen3.5-14B",
|
||||
"gguf_file": "qwen3.5-14b-q4_k_m.gguf",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-14B-4bit",
|
||||
"hf_repo": "Qwen/Qwen3.5-9B",
|
||||
"gguf_file": "qwen3.5-9b-q4_k_m.gguf",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-9B-MLX-4bit",
|
||||
},
|
||||
),
|
||||
ModelSpec(
|
||||
model_id="qwen3.5:27b",
|
||||
name="Qwen3.5 27B",
|
||||
parameter_count_b=27.0,
|
||||
active_parameter_count_b=3.0,
|
||||
context_length=131072,
|
||||
min_vram_gb=16.0,
|
||||
supported_engines=("ollama", "vllm", "llamacpp", "sglang", "mlx"),
|
||||
provider="alibaba",
|
||||
metadata={
|
||||
"architecture": "moe",
|
||||
"hf_repo": "Qwen/Qwen3.5-27B",
|
||||
"gguf_file": "qwen3.5-27b-q4_k_m.gguf",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-27B-4bit-DWQ",
|
||||
},
|
||||
),
|
||||
ModelSpec(
|
||||
@@ -235,7 +249,7 @@ BUILTIN_MODELS: List[ModelSpec] = [
|
||||
"architecture": "moe",
|
||||
"hf_repo": "Qwen/Qwen3.5-4B",
|
||||
"gguf_file": "qwen3.5-4b-q4_k_m.gguf",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-4B-4bit",
|
||||
"mlx_repo": "mlx-community/Qwen3.5-4B-OptiQ-4bit",
|
||||
},
|
||||
),
|
||||
ModelSpec(
|
||||
@@ -490,8 +504,7 @@ BUILTIN_MODELS: List[ModelSpec] = [
|
||||
metadata={
|
||||
"architecture": "moe",
|
||||
"hf_repo": (
|
||||
"TeichAI/GLM-4.7-Flash-Claude-"
|
||||
"Opus-4.5-High-Reasoning-Distill-GGUF"
|
||||
"TeichAI/GLM-4.7-Flash-Claude-Opus-4.5-High-Reasoning-Distill-GGUF"
|
||||
),
|
||||
"teacher": "Claude Opus 4.5",
|
||||
"quantization": "GGUF Q4_K_M / Q8_0",
|
||||
|
||||
@@ -23,9 +23,7 @@ class TestInitShowsNextSteps:
|
||||
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "llamacpp", _NO_DL]
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
|
||||
assert result.exit_code == 0
|
||||
assert "Getting Started" in result.output
|
||||
assert "jarvis ask" in result.output
|
||||
@@ -40,9 +38,7 @@ class TestInitShowsNextSteps:
|
||||
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "llamacpp", _NO_DL]
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
|
||||
assert result.exit_code == 0
|
||||
assert "[engine]" in result.output
|
||||
assert "[intelligence]" in result.output
|
||||
@@ -57,12 +53,12 @@ class TestNextStepsOllama:
|
||||
assert "jarvis doctor" in text
|
||||
|
||||
def test_next_steps_ollama_with_model(self) -> None:
|
||||
text = _next_steps_text("ollama", "qwen3.5:14b")
|
||||
assert "ollama pull qwen3.5:14b" in text
|
||||
text = _next_steps_text("ollama", "qwen3.5:27b")
|
||||
assert "ollama pull qwen3.5:27b" in text
|
||||
|
||||
def test_next_steps_ollama_default_model(self) -> None:
|
||||
text = _next_steps_text("ollama")
|
||||
assert "ollama pull qwen3.5:3b" in text
|
||||
assert "ollama pull qwen3.5:2b" in text
|
||||
|
||||
|
||||
class TestNextStepsVllm:
|
||||
@@ -102,9 +98,7 @@ class TestMinimalConfig:
|
||||
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "ollama", _NO_DL]
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "ollama", _NO_DL])
|
||||
assert result.exit_code == 0
|
||||
content = config_path.read_text()
|
||||
# Minimal config should be short
|
||||
@@ -158,9 +152,7 @@ class TestInitDownloadPrompt:
|
||||
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "ollama", _NO_DL]
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "ollama", _NO_DL])
|
||||
assert result.exit_code == 0
|
||||
assert "Download" not in result.output
|
||||
|
||||
@@ -175,13 +167,10 @@ class TestInitEmptyModelFallback:
|
||||
mock.patch("openjarvis.cli.init_cmd.recommend_model", return_value=""),
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "llamacpp"]
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp"])
|
||||
assert result.exit_code == 0
|
||||
assert (
|
||||
"Not enough memory" in result.output
|
||||
or "not enough memory" in result.output
|
||||
"Not enough memory" in result.output or "not enough memory" in result.output
|
||||
)
|
||||
|
||||
|
||||
@@ -200,9 +189,7 @@ class TestNextStepsExoNexa:
|
||||
|
||||
|
||||
class TestInitDownloadDispatch:
|
||||
def test_init_ollama_download_calls_ollama_pull(
|
||||
self, tmp_path: Path
|
||||
) -> None:
|
||||
def test_init_ollama_download_calls_ollama_pull(self, tmp_path: Path) -> None:
|
||||
config_dir = tmp_path / ".openjarvis"
|
||||
config_path = config_dir / "config.toml"
|
||||
with (
|
||||
@@ -228,9 +215,7 @@ class TestInitDownloadDispatch:
|
||||
mock.patch("openjarvis.cli.init_cmd.DEFAULT_CONFIG_PATH", config_path),
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner"),
|
||||
):
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "vllm"], input="y\n"
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "vllm"], input="y\n")
|
||||
assert result.exit_code == 0
|
||||
assert "automatically" in result.output
|
||||
|
||||
@@ -245,12 +230,11 @@ class TestInitPrivacyHook:
|
||||
mock.patch("openjarvis.cli.init_cmd.PrivacyScanner") as MockScanner,
|
||||
):
|
||||
from openjarvis.cli.scan_cmd import ScanResult
|
||||
|
||||
instance = MockScanner.return_value
|
||||
instance.run_quick.return_value = [
|
||||
ScanResult("FileVault", "ok", "FileVault enabled", "darwin"),
|
||||
]
|
||||
result = CliRunner().invoke(
|
||||
cli, ["init", "--engine", "llamacpp", _NO_DL]
|
||||
)
|
||||
result = CliRunner().invoke(cli, ["init", "--engine", "llamacpp", _NO_DL])
|
||||
assert result.exit_code == 0
|
||||
assert "jarvis scan" in result.output
|
||||
|
||||
@@ -15,6 +15,7 @@ class TestOllamaPull:
|
||||
|
||||
def test_ollama_pull_success(self) -> None:
|
||||
import io
|
||||
|
||||
console = Console(file=io.StringIO())
|
||||
mock_lines = [
|
||||
'{"status": "pulling manifest"}',
|
||||
@@ -28,7 +29,7 @@ class TestOllamaPull:
|
||||
mock_resp.__exit__ = mock.MagicMock(return_value=False)
|
||||
|
||||
with mock.patch("httpx.stream", return_value=mock_resp):
|
||||
result = ollama_pull("http://localhost:11434", "qwen3.5:3b", console)
|
||||
result = ollama_pull("http://localhost:11434", "qwen3.5:2b", console)
|
||||
assert result is True
|
||||
|
||||
def test_ollama_pull_connect_error(self) -> None:
|
||||
@@ -38,7 +39,7 @@ class TestOllamaPull:
|
||||
|
||||
console = Console(file=io.StringIO())
|
||||
with mock.patch("httpx.stream", side_effect=httpx.ConnectError("refused")):
|
||||
result = ollama_pull("http://localhost:11434", "qwen3.5:3b", console)
|
||||
result = ollama_pull("http://localhost:11434", "qwen3.5:2b", console)
|
||||
assert result is False
|
||||
|
||||
|
||||
@@ -58,14 +59,14 @@ class TestPullCliMultiEngine:
|
||||
mock_run.return_value = mock.MagicMock(returncode=0)
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["model", "pull", "qwen3.5:8b", "--engine", "llamacpp"]
|
||||
cli, ["model", "pull", "qwen3.5:9b", "--engine", "llamacpp"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_run.assert_called_once()
|
||||
call_args = mock_run.call_args[0][0]
|
||||
assert "huggingface-cli" in call_args
|
||||
assert "qwen3.5-8b-q4_k_m.gguf" in call_args
|
||||
assert "qwen3.5-9b-q4_k_m.gguf" in call_args
|
||||
|
||||
def test_pull_mlx_uses_huggingface_cli(self) -> None:
|
||||
from openjarvis.cli import cli
|
||||
@@ -80,14 +81,14 @@ class TestPullCliMultiEngine:
|
||||
mock_run.return_value = mock.MagicMock(returncode=0)
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["model", "pull", "qwen3.5:8b", "--engine", "mlx"]
|
||||
cli, ["model", "pull", "qwen3.5:9b", "--engine", "mlx"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
mock_run.assert_called_once()
|
||||
call_args = mock_run.call_args[0][0]
|
||||
assert "huggingface-cli" in call_args
|
||||
assert "mlx-community/Qwen3.5-8B-4bit" in call_args
|
||||
assert "mlx-community/Qwen3.5-9B-MLX-4bit" in call_args
|
||||
|
||||
def test_pull_llamacpp_huggingface_cli_not_found(self) -> None:
|
||||
from openjarvis.cli import cli
|
||||
@@ -101,7 +102,7 @@ class TestPullCliMultiEngine:
|
||||
mock_cfg.return_value.engine.ollama_host = None
|
||||
|
||||
result = runner.invoke(
|
||||
cli, ["model", "pull", "qwen3.5:8b", "--engine", "llamacpp"]
|
||||
cli, ["model", "pull", "qwen3.5:9b", "--engine", "llamacpp"]
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestRecommendModelGpu:
|
||||
result = recommend_model(hw, "ollama")
|
||||
# 14B * 0.5 * 1.1 = 7.7 GB; available = 8 * 0.9 = 7.2 → too big
|
||||
# 8B * 0.5 * 1.1 = 4.4 GB; available = 7.2 → fits
|
||||
assert result == "qwen3.5:8b"
|
||||
assert result == "qwen3.5:9b"
|
||||
|
||||
def test_4gb_gpu_picks_qwen35_4b(self) -> None:
|
||||
hw = HardwareInfo(
|
||||
@@ -47,7 +47,7 @@ class TestRecommendModelGpu:
|
||||
)
|
||||
result = recommend_model(hw, "ollama")
|
||||
# 3B * 0.5 * 1.1 = 1.65 GB; available = 2 * 0.9 = 1.8 → fits
|
||||
assert result == "qwen3.5:3b"
|
||||
assert result == "qwen3.5:2b"
|
||||
|
||||
def test_multi_gpu_picks_larger_model(self) -> None:
|
||||
hw = HardwareInfo(
|
||||
@@ -80,8 +80,9 @@ class TestRecommendModelCpuOnly:
|
||||
hw = HardwareInfo(platform="linux", ram_gb=16.0, gpu=None)
|
||||
result = recommend_model(hw, "llamacpp")
|
||||
# available = (16 - 4) * 0.8 = 9.6 GB
|
||||
# 14B * 0.5 * 1.1 = 7.7 → fits
|
||||
assert result == "qwen3.5:14b"
|
||||
# 27B * 0.5 * 1.1 = 14.85 → too big
|
||||
# 9B * 0.5 * 1.1 = 4.95 → fits
|
||||
assert result == "qwen3.5:9b"
|
||||
|
||||
def test_cpu_only_8gb_ram(self) -> None:
|
||||
hw = HardwareInfo(platform="linux", ram_gb=8.0, gpu=None)
|
||||
@@ -127,7 +128,7 @@ class TestRecommendModelMlx:
|
||||
gpu=GpuInfo(vendor="apple", name="Apple M1", vram_gb=8.0, count=1),
|
||||
)
|
||||
result = recommend_model(hw, "mlx")
|
||||
assert result == "qwen3.5:8b"
|
||||
assert result == "qwen3.5:9b"
|
||||
|
||||
def test_apple_silicon_16gb_mlx(self) -> None:
|
||||
hw = HardwareInfo(
|
||||
@@ -136,7 +137,10 @@ class TestRecommendModelMlx:
|
||||
gpu=GpuInfo(vendor="apple", name="Apple M2", vram_gb=16.0, count=1),
|
||||
)
|
||||
result = recommend_model(hw, "mlx")
|
||||
assert result == "qwen3.5:14b"
|
||||
# available = 16 * 0.9 = 14.4 GB
|
||||
# 27B * 0.5 * 1.1 = 14.85 → too big
|
||||
# 9B * 0.5 * 1.1 = 4.95 → fits
|
||||
assert result == "qwen3.5:9b"
|
||||
|
||||
def test_apple_silicon_32gb_mlx(self) -> None:
|
||||
hw = HardwareInfo(
|
||||
@@ -145,7 +149,7 @@ class TestRecommendModelMlx:
|
||||
gpu=GpuInfo(vendor="apple", name="Apple M2 Pro", vram_gb=32.0, count=1),
|
||||
)
|
||||
result = recommend_model(hw, "mlx")
|
||||
assert result == "qwen3.5:14b"
|
||||
assert result == "qwen3.5:27b"
|
||||
|
||||
def test_apple_silicon_64gb_mlx(self) -> None:
|
||||
hw = HardwareInfo(
|
||||
@@ -154,4 +158,4 @@ class TestRecommendModelMlx:
|
||||
gpu=GpuInfo(vendor="apple", name="Apple M2 Max", vram_gb=64.0, count=1),
|
||||
)
|
||||
result = recommend_model(hw, "mlx")
|
||||
assert result == "qwen3.5:14b"
|
||||
assert result == "qwen3.5:27b"
|
||||
|
||||
@@ -186,26 +186,26 @@ class TestCloudModelSpecs:
|
||||
class TestQwen35ModelSpecs:
|
||||
"""Verify Qwen3.5 MoE model entries."""
|
||||
|
||||
def test_qwen35_3b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:3b")
|
||||
assert spec.parameter_count_b == 3.0
|
||||
assert spec.active_parameter_count_b == 0.6
|
||||
def test_qwen35_2b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:2b")
|
||||
assert spec.parameter_count_b == 2.0
|
||||
assert spec.active_parameter_count_b == 0.4
|
||||
assert spec.context_length == 131072
|
||||
assert spec.provider == "alibaba"
|
||||
assert spec.metadata["architecture"] == "moe"
|
||||
for e in ("ollama", "vllm", "llamacpp", "sglang"):
|
||||
assert e in spec.supported_engines
|
||||
|
||||
def test_qwen35_8b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:8b")
|
||||
assert spec.parameter_count_b == 8.0
|
||||
assert spec.active_parameter_count_b == 1.0
|
||||
def test_qwen35_9b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:9b")
|
||||
assert spec.parameter_count_b == 9.0
|
||||
assert spec.active_parameter_count_b == 1.5
|
||||
assert spec.context_length == 131072
|
||||
|
||||
def test_qwen35_14b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:14b")
|
||||
assert spec.parameter_count_b == 14.0
|
||||
assert spec.active_parameter_count_b == 2.0
|
||||
def test_qwen35_27b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:27b")
|
||||
assert spec.parameter_count_b == 27.0
|
||||
assert spec.active_parameter_count_b == 3.0
|
||||
|
||||
def test_qwen35_35b(self) -> None:
|
||||
spec = _get_spec("qwen3.5:35b")
|
||||
@@ -300,9 +300,9 @@ class TestModelDiscovery:
|
||||
"gpt-oss:120b",
|
||||
"glm-4.7-flash",
|
||||
"trinity-mini",
|
||||
"qwen3.5:3b",
|
||||
"qwen3.5:8b",
|
||||
"qwen3.5:14b",
|
||||
"qwen3.5:2b",
|
||||
"qwen3.5:9b",
|
||||
"qwen3.5:27b",
|
||||
"qwen3.5:35b",
|
||||
"qwen3.5:122b",
|
||||
"qwen3.5:397b",
|
||||
|
||||
Reference in New Issue
Block a user