mirror of https://github.com/razor-ai/soup.git
180 lines
6.5 KiB
Python
180 lines
6.5 KiB
Python
"""Tests for ONNX and TensorRT-LLM export — config, validation, CLI."""
|
|
|
|
from unittest.mock import MagicMock
|
|
from unittest.mock import patch as mock_patch
|
|
|
|
from soup_cli.commands.export import SUPPORTED_FORMATS
|
|
|
|
# ─── Format Support Tests ────────────────────────────────────────────────
|
|
|
|
|
|
class TestExportFormats:
|
|
"""Test that new export formats are registered."""
|
|
|
|
def test_gguf_format_supported(self):
|
|
assert "gguf" in SUPPORTED_FORMATS
|
|
|
|
def test_onnx_format_supported(self):
|
|
assert "onnx" in SUPPORTED_FORMATS
|
|
|
|
def test_tensorrt_format_supported(self):
|
|
assert "tensorrt" in SUPPORTED_FORMATS
|
|
|
|
def test_format_count(self):
|
|
"""v0.53.1 — 7 prior formats + torchao + gguf-ud = 9."""
|
|
assert len(SUPPORTED_FORMATS) == 9
|
|
assert "bitnet" in SUPPORTED_FORMATS
|
|
assert "tq1_0" in SUPPORTED_FORMATS
|
|
assert "torchao" in SUPPORTED_FORMATS
|
|
assert "gguf-ud" in SUPPORTED_FORMATS
|
|
|
|
|
|
# ─── ONNX Export CLI Tests ──────────────────────────────────────────────
|
|
|
|
|
|
class TestOnnxExportCLI:
|
|
"""Test ONNX export via CLI."""
|
|
|
|
def test_onnx_export_missing_model_path(self, tmp_path):
|
|
"""soup export --format onnx should fail if model path doesn't exist."""
|
|
from typer.testing import CliRunner
|
|
|
|
from soup_cli.cli import app
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
app, ["export", "--model", str(tmp_path / "nonexistent"), "--format", "onnx"]
|
|
)
|
|
assert result.exit_code != 0
|
|
|
|
def test_onnx_export_format_in_help(self):
|
|
"""Export help should mention onnx format."""
|
|
from typer.testing import CliRunner
|
|
|
|
from soup_cli.cli import app
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(app, ["export", "--help"])
|
|
assert "onnx" in result.output.lower()
|
|
|
|
def test_tensorrt_export_format_in_help(self):
|
|
"""Export help should mention tensorrt format."""
|
|
from typer.testing import CliRunner
|
|
|
|
from soup_cli.cli import app
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(app, ["export", "--help"])
|
|
assert "tensorrt" in result.output.lower()
|
|
|
|
|
|
# ─── ONNX Export Function Tests ─────────────────────────────────────────
|
|
|
|
|
|
class TestOnnxExportFunction:
|
|
"""Test _export_onnx logic."""
|
|
|
|
def test_export_onnx_calls_main_export(self, tmp_path):
|
|
"""_export_onnx should call optimum's main_export."""
|
|
model_dir = tmp_path / "model"
|
|
model_dir.mkdir()
|
|
|
|
mock_main_export = MagicMock()
|
|
with mock_patch(
|
|
"soup_cli.commands.export.main_export",
|
|
mock_main_export,
|
|
create=True,
|
|
):
|
|
# Need to patch the import inside the function
|
|
import soup_cli.commands.export as export_mod
|
|
original = getattr(export_mod, "main_export", None)
|
|
try:
|
|
# Inject mock at module level for the lazy import
|
|
with mock_patch.object(
|
|
export_mod, "_export_onnx",
|
|
wraps=export_mod._export_onnx,
|
|
):
|
|
# Patch the actual import
|
|
with mock_patch.dict("sys.modules", {
|
|
"optimum": MagicMock(),
|
|
"optimum.exporters": MagicMock(),
|
|
"optimum.exporters.onnx": MagicMock(
|
|
main_export=mock_main_export
|
|
),
|
|
}):
|
|
export_mod._export_onnx(model_dir, str(tmp_path / "out"), None)
|
|
mock_main_export.assert_called_once()
|
|
finally:
|
|
if original is not None:
|
|
export_mod.main_export = original
|
|
|
|
def test_export_onnx_default_output_path(self, tmp_path):
|
|
"""Default output path should be model_name + _onnx suffix."""
|
|
model_dir = tmp_path / "my_model"
|
|
model_dir.mkdir()
|
|
|
|
mock_main_export = MagicMock()
|
|
with mock_patch.dict("sys.modules", {
|
|
"optimum": MagicMock(),
|
|
"optimum.exporters": MagicMock(),
|
|
"optimum.exporters.onnx": MagicMock(main_export=mock_main_export),
|
|
}):
|
|
import soup_cli.commands.export as export_mod
|
|
|
|
export_mod._export_onnx(model_dir, None, None)
|
|
call_kwargs = mock_main_export.call_args[1]
|
|
assert "my_model_onnx" in call_kwargs["output"]
|
|
|
|
|
|
# ─── TensorRT Export Function Tests ──────────────────────────────────────
|
|
|
|
|
|
class TestTensorrtExportFunction:
|
|
"""Test _export_tensorrt logic."""
|
|
|
|
def test_tensorrt_export_calls_subprocess(self, tmp_path):
|
|
"""TensorRT export should call subprocess for checkpoint conversion."""
|
|
model_dir = tmp_path / "model"
|
|
model_dir.mkdir()
|
|
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 0
|
|
|
|
with mock_patch.dict("sys.modules", {
|
|
"optimum": MagicMock(),
|
|
"optimum.exporters": MagicMock(),
|
|
"optimum.exporters.onnx": MagicMock(),
|
|
"tensorrt_llm": MagicMock(),
|
|
}), mock_patch("subprocess.run", return_value=mock_result) as mock_run:
|
|
import soup_cli.commands.export as export_mod
|
|
|
|
export_mod._export_tensorrt(
|
|
model_dir, str(tmp_path / "trt_out"), None
|
|
)
|
|
# Should call subprocess at least twice (checkpoint + build)
|
|
assert mock_run.call_count >= 2
|
|
|
|
|
|
# ─── Unsupported Format Test ─────────────────────────────────────────────
|
|
|
|
|
|
class TestExportUnsupportedFormat:
|
|
"""Test that unsupported formats are rejected."""
|
|
|
|
def test_invalid_format_rejected(self, tmp_path):
|
|
"""Export with unsupported format should exit with error."""
|
|
from typer.testing import CliRunner
|
|
|
|
from soup_cli.cli import app
|
|
|
|
model_dir = tmp_path / "model"
|
|
model_dir.mkdir()
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(
|
|
app,
|
|
["export", "--model", str(model_dir), "--format", "safetensors"],
|
|
)
|
|
assert result.exit_code != 0
|
|
assert "Unsupported format" in result.output
|