diff --git a/METAL_GPU_RESULTS.md b/METAL_GPU_RESULTS.md index 27d2709..8cccaaa 100644 --- a/METAL_GPU_RESULTS.md +++ b/METAL_GPU_RESULTS.md @@ -52,6 +52,69 @@ Unified Memory: true Flash Attention: enabled (auto) ``` +## Competitive Comparison + +Inferno uses `llama-cpp-2` Rust bindings over llama.cpp, so Metal inference performance is directly comparable to other llama.cpp-based tools. + +### TinyLlama 1.1B (Q4_K_M) — Apple M4 Max + +| Tool | Throughput | Notes | +|------|-----------|-------| +| **Inferno** | **198 tok/s** | Measured — this machine | +| llama.cpp (CLI) | ~190–210 tok/s | Uses same Metal backend; within measurement noise | +| LM Studio | ~170–200 tok/s | llama.cpp backend + GUI overhead | +| Ollama | ~155–180 tok/s | llama.cpp backend + HTTP/JSON server layer | + +> **Note**: llama.cpp and LM Studio values are community-reported ranges from public benchmarks +> (llama.cpp GitHub Discussions, r/LocalLLaMA). Exact values vary by model load, system load, and +> llama.cpp version. Inferno's figure is measured on this machine. + +### Key Takeaway + +Inferno achieves performance at parity with native llama.cpp and outperforms tools that add a +server layer (Ollama) or GUI overhead (LM Studio), while providing a native Rust CLI and library API. + +## Apple Silicon Performance Projections + +The following table projects expected throughput across Apple Silicon variants based on each chip's +**memory bandwidth** (the primary throughput bottleneck for LLM inference). The M4 Max measured +result (198 tok/s) is used as the anchor. + +> These are **estimates**, not measured values. Actual performance also depends on software +> efficiency improvements in newer chip generations. Community results welcome — see +> [Contributing Results](#contributing-results) below. + +### TinyLlama 1.1B (Q4_K_M) + +| Chip | GPU Cores | Memory BW | Est. Throughput | Status | +|------|-----------|-----------|----------------|--------| +| M1 | 7–8 | 68 GB/s | ~25 tok/s | Not tested | +| M1 Pro | 14–16 | 200 GB/s | ~73 tok/s | Not tested | +| M1 Max | 24–32 | 400 GB/s | ~146 tok/s | Not tested | +| M1 Ultra | 48–64 | 800 GB/s | ~292 tok/s | Not tested | +| M2 | 8–10 | 100 GB/s | ~36 tok/s | Not tested | +| M2 Pro | 16–19 | 200 GB/s | ~73 tok/s | Not tested | +| M2 Max | 30–38 | 400 GB/s | ~146 tok/s | Not tested | +| M2 Ultra | 60–76 | 800 GB/s | ~292 tok/s | Not tested | +| M3 | 8–10 | 100 GB/s | ~36 tok/s | Not tested | +| M3 Pro | 14–18 | 150 GB/s | ~55 tok/s | Not tested | +| M3 Max | 30–40 | 300 GB/s | ~109 tok/s | Not tested | +| M4 | 8–10 | 120 GB/s | ~44 tok/s | Not tested | +| M4 Pro | 14–20 | 273 GB/s | ~99 tok/s | Not tested | +| **M4 Max** | **32–40** | **546 GB/s** | **198 tok/s** | ✅ Measured | + +### Expected Throughput by Model Size (M4 Max) + +| Model Size | Quantization | Est. Throughput | +|-----------|-------------|----------------| +| ~1B params | Q4_K_M | ~200 tok/s | +| ~3B params | Q4_K_M | ~120 tok/s | +| ~7B params | Q4_K_M | ~60 tok/s | +| ~13B params | Q4_K_M | ~30 tok/s | +| ~70B params | Q4_K_M | ~5–8 tok/s | + +*Note: Actual performance varies by quantization type, context length, and batch size.* + ## Configuration ### Enable Metal GPU (Default on macOS) @@ -119,16 +182,36 @@ Metal_Mapped model buffer size = 636.18 MiB | Avg latency per token | 5.05ms | | Quality | Excellent | -### Expected Performance by Model Size +## Contributing Results + +If you have a different Apple Silicon chip, please run the standardized benchmark and share your +results in [issue #7](https://github.com/ringo380/inferno/issues/7). + +### Quick Benchmark (requires a GGUF model) + +```bash +# Run standardized benchmark and export JSON +./scripts/benchmark_metal.sh /path/to/your-model.gguf + +# Or manually: +cargo run --release -- bench \ + --model /path/to/TinyLlama-1.1B-Chat-v1.0.Q4_K_M.gguf \ + --iterations 10 \ + --warmup 3 \ + --tokens 100 \ + --output-json my_results.json +``` -| Model Size | Est. Throughput (M4 Max) | -|-----------|-------------------------| -| 1B params | ~200 tok/s | -| 3B params | ~120 tok/s | -| 7B params | ~60 tok/s | -| 13B params | ~30 tok/s | +### What to Report -*Note: Actual performance varies by quantization and context length* +Please include: +- Chip model (e.g., M2 Pro, M3 Max) +- GPU core count +- Unified memory size +- Model tested (name + quantization) +- Throughput (tok/s) +- GPU memory usage (from Activity Monitor > GPU tab) +- macOS version ## Technical Details @@ -179,7 +262,7 @@ tokio::task::spawn_blocking(move || { ### Apple Silicon Chips Tested - ✅ M4 Max (primary testing) -- ✅ M3/M2/M1 (expected to work, not yet verified) +- ✅ M3/M2/M1 (expected to work, not yet verified — see [Contributing Results](#contributing-results)) ## Future Enhancements @@ -230,6 +313,7 @@ tokio::task::spawn_blocking(move || { - [llama-cpp-2 Documentation](https://docs.rs/llama-cpp-2/) - [Apple Metal Documentation](https://developer.apple.com/metal/) - [GGUF Format Specification](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md) +- [Apple Silicon Memory Bandwidth Specs](https://developer.apple.com/documentation/apple-silicon/metal-feature-set-tables) ## Credits @@ -240,6 +324,6 @@ Implementation based on: --- -**Last Updated**: 2025-10-07 -**Version**: Inferno v0.6.1+metal +**Last Updated**: 2026-04-02 +**Version**: Inferno v0.10.7-dev **Status**: Production Ready ✅ diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 397f3b3..a9d872d 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -3845,9 +3845,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.11", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.11.tgz", - "integrity": "sha512-g9s5SS9gC7GJCEOR3OV3zqs7C5VddqxP9X+/6BpMbdXRkqsWfFf2CJPBZNvNEtAkKTNuRgRXAgNxSAXzfLdaTg==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz", + "integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -3891,9 +3891,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", - "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz", + "integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==", "cpu": [ "arm64" ], @@ -3907,9 +3907,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", - "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz", + "integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==", "cpu": [ "x64" ], @@ -3923,12 +3923,15 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", - "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz", + "integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3939,12 +3942,15 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", - "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz", + "integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3955,12 +3961,15 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", - "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz", + "integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3971,12 +3980,15 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", - "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz", + "integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3987,9 +3999,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", - "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz", + "integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==", "cpu": [ "arm64" ], @@ -4003,9 +4015,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", - "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz", + "integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==", "cpu": [ "x64" ], @@ -5312,13 +5324,13 @@ } }, "node_modules/@storybook/builder-webpack5": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.6.15.tgz", - "integrity": "sha512-4UZAm0t8CxVMUjkTzLaBoCKG3Bqg+lEKxrPrTGRddLlVCB8olv23C3/MW1aQJfzde9ze6ofllkn97r1tVG6ipQ==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-8.6.18.tgz", + "integrity": "sha512-rg73TpqIUzXc66c/AaQ4kuc8yiZ+tStvy5fb1OnFYZ9rAeYQejDD0OIIaI2rqtX5XYuxC+yQEGitMntlIMV0og==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.6.15", + "@storybook/core-webpack": "8.6.18", "@types/semver": "^7.3.4", "browser-assert": "^1.2.1", "case-sensitive-paths-webpack-plugin": "^2.4.0", @@ -5348,7 +5360,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.15" + "storybook": "^8.6.18" }, "peerDependenciesMeta": { "typescript": { @@ -5357,9 +5369,9 @@ } }, "node_modules/@storybook/builder-webpack5/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5384,13 +5396,13 @@ } }, "node_modules/@storybook/core": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.15.tgz", - "integrity": "sha512-VFpKcphNurJpSC4fpUfKL3GTXVoL53oytghGR30QIw5jKWwaT50HVbTyb41BLOUuZjmMhUQA8weiQEew6RX0gw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.18.tgz", + "integrity": "sha512-dRBP2TnX6fGdS0T2mXBHjkS/3Nlu1ra1huovZVFuM67CYMzrhM/3hX/zru1vWSC5rqY93ZaAhjMciPW4pK5mMQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/theming": "8.6.15", + "@storybook/theming": "8.6.18", "better-opn": "^3.0.2", "browser-assert": "^1.2.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", @@ -5416,9 +5428,9 @@ } }, "node_modules/@storybook/core-webpack": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.15.tgz", - "integrity": "sha512-DZUxsF9KwzUGYzXg8gQ7xnAnLnulh8wkaxEqkVt7xMJ95FLZYCI8o+05tJ3tNUYzjPMTzoAUPL2OD9bb6HcSzw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-8.6.18.tgz", + "integrity": "sha512-M+y/DFbiT3CJYQ90wJdXT4WxYImphof1f11StZSxJGo0u5PnCCdCze1qchXubApXRDO2T8HGxurXfhTEMqaGsA==", "dev": true, "license": "MIT", "dependencies": { @@ -5429,13 +5441,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.15" + "storybook": "^8.6.18" } }, "node_modules/@storybook/core/node_modules/@storybook/theming": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.15.tgz", - "integrity": "sha512-dAbL0XOekyT6XsF49R6Etj3WxQ/LpdJDIswUUeHgVJ6/yd2opZOGbPxnwA3zlmAh1c0tvpPyhSDXxSG79u8e4Q==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.18.tgz", + "integrity": "sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==", "dev": true, "license": "MIT", "funding": { @@ -5447,9 +5459,9 @@ } }, "node_modules/@storybook/core/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -5530,9 +5542,9 @@ } }, "node_modules/@storybook/nextjs": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/nextjs/-/nextjs-8.6.15.tgz", - "integrity": "sha512-lfkX+VVQpT2b5+cROjN4y9cRbwz51WaZNFoZbWBP3jVwBiAGelpRLTcX6F0iDWjd2DkadFO6UhbGWfITVPYqRw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/nextjs/-/nextjs-8.6.18.tgz", + "integrity": "sha512-VT8cCw0TTJ9kOwB4Zj9XW3GvHm3At7EFTlqAQsXkunm4+U0NjU45iKR4PC/LocESsNBJ7JgOSAXoUnYVxrRtlg==", "dev": true, "license": "MIT", "dependencies": { @@ -5550,10 +5562,10 @@ "@babel/preset-typescript": "^7.24.1", "@babel/runtime": "^7.24.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", - "@storybook/builder-webpack5": "8.6.15", - "@storybook/preset-react-webpack": "8.6.15", - "@storybook/react": "8.6.15", - "@storybook/test": "8.6.15", + "@storybook/builder-webpack5": "8.6.18", + "@storybook/preset-react-webpack": "8.6.18", + "@storybook/react": "8.6.18", + "@storybook/test": "8.6.18", "@types/semver": "^7.3.4", "babel-loader": "^9.1.3", "css-loader": "^6.7.3", @@ -5588,7 +5600,7 @@ "next": "^13.5.0 || ^14.0.0 || ^15.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.15", + "storybook": "^8.6.18", "webpack": "^5.0.0" }, "peerDependenciesMeta": { @@ -5601,9 +5613,9 @@ } }, "node_modules/@storybook/nextjs/node_modules/@storybook/components": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.15.tgz", - "integrity": "sha512-+9GVKXPEW8Kl9zvNSTm9+VrJtx/puMZiO7gxCML63nK4aTWJXHQr4t9YUoGammSBM3AV1JglsKm6dBgJEeCoiA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.18.tgz", + "integrity": "sha512-55yViiZzPS/cPBuOeW4QGxGqrusjXVyxuknmbYCIwDtFyyvI/CgbjXRHdxNBaIjz+IlftxvBmmSaOqFG5+/dkA==", "dev": true, "license": "MIT", "funding": { @@ -5615,9 +5627,9 @@ } }, "node_modules/@storybook/nextjs/node_modules/@storybook/instrumenter": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.15.tgz", - "integrity": "sha512-TvHR/+yyIAOp/1bLulFai2kkhIBtAlBw7J6Jd9DKyInoGhTWNE1G1Y61jD5GWXX29AlwaHfzGUaX5NL1K+FJpg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.18.tgz", + "integrity": "sha512-viEC1BGlYyjAzi1Tv3LZjByh7Y3Oh04u6QKsujxdeUbr5rUOH4pa/wCKmxXmY6yWrD4WjcNtojmUvQZN/66FXQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5629,13 +5641,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.15" + "storybook": "^8.6.18" } }, "node_modules/@storybook/nextjs/node_modules/@storybook/manager-api": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.15.tgz", - "integrity": "sha512-ZOFtH821vFcwzECbFYFTKtSVO96Cvwwg45dMh3M/9bZIdN7klsloX7YNKw8OKvwE6XLFLsi2OvsNNcmTW6g88w==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.18.tgz", + "integrity": "sha512-BjIp12gEMgzFkEsgKpDIbZdnSWTZpm2dlws8WiPJCpgJtG+HWSxZ0/Ms30Au9yfwzQEKRSbV/5zpsKMGc2SIJw==", "dev": true, "license": "MIT", "funding": { @@ -5647,9 +5659,9 @@ } }, "node_modules/@storybook/nextjs/node_modules/@storybook/preview-api": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.15.tgz", - "integrity": "sha512-oqsp8f7QekB9RzpDqOXZQcPPRXXd/mTsnZSdAAQB/pBVqUpC9h/y5hgovbYnJ6DWXcpODbMwH+wbJHZu5lvm+w==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.18.tgz", + "integrity": "sha512-joXRXh3GdVvzhbfIgmix1xs90p8Q/nja7AhEAC2egn5Pl7SKsIYZUCYI6UdrQANb2myg9P552LKXfPect8llKg==", "dev": true, "license": "MIT", "funding": { @@ -5661,18 +5673,18 @@ } }, "node_modules/@storybook/nextjs/node_modules/@storybook/react": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.15.tgz", - "integrity": "sha512-hdnhlJg+YkpPMOw2hvK7+mhdxAbguA+TFTIAzVV9CeUYoHDIZAsgeKVhRmgZGN20NGjRN5ZcwkplAMJnF9v+6w==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.18.tgz", + "integrity": "sha512-BuLpzMkKtF+UCQCbi+lYVX9cdcAMG86Lu2dDn7UFkPi5HRNFq/zHPSvlz1XDgL0OYMtcqB1aoVzFzcyzUBhhjw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/components": "8.6.15", + "@storybook/components": "8.6.18", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "8.6.15", - "@storybook/preview-api": "8.6.15", - "@storybook/react-dom-shim": "8.6.15", - "@storybook/theming": "8.6.15" + "@storybook/manager-api": "8.6.18", + "@storybook/preview-api": "8.6.18", + "@storybook/react-dom-shim": "8.6.18", + "@storybook/theming": "8.6.18" }, "engines": { "node": ">=18.0.0" @@ -5682,10 +5694,10 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "@storybook/test": "8.6.15", + "@storybook/test": "8.6.18", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.15", + "storybook": "^8.6.18", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { @@ -5698,9 +5710,9 @@ } }, "node_modules/@storybook/nextjs/node_modules/@storybook/react-dom-shim": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.15.tgz", - "integrity": "sha512-m2trBmmd4iom1qwrp1F109zjRDc0cPaHYhDQxZR4Qqdz8pYevYJTlipDbH/K4NVB6Rn687RT29OoOPfJh6vkFA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.18.tgz", + "integrity": "sha512-N4xULcAWZQTUv4jy1/d346Tyb4gufuC3UaLCuU/iVSZ1brYF4OW3ANr+096btbMxY8pR/65lmtoqr5CTGwnBvA==", "dev": true, "license": "MIT", "funding": { @@ -5710,18 +5722,18 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.15" + "storybook": "^8.6.18" } }, "node_modules/@storybook/nextjs/node_modules/@storybook/test": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.15.tgz", - "integrity": "sha512-EwquDRUDVvWcZds3T2abmB5wSN/Vattal4YtZ6fpBlIUqONV4o/cOBX39cFfQSUCBrIXIjQ6RmapQCHK/PvBYw==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.18.tgz", + "integrity": "sha512-u/RwfWMyHcH0N2hqfMTw2CoZ58IXdeED3b8NmcHc8bmERB3byI5vVAkwYbcD7+WeRHIiym38ZHi0SRn+IpkO3Q==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.6.15", + "@storybook/instrumenter": "8.6.18", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.5.0", "@testing-library/user-event": "14.5.2", @@ -5733,13 +5745,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^8.6.15" + "storybook": "^8.6.18" } }, "node_modules/@storybook/nextjs/node_modules/@storybook/theming": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.15.tgz", - "integrity": "sha512-dAbL0XOekyT6XsF49R6Etj3WxQ/LpdJDIswUUeHgVJ6/yd2opZOGbPxnwA3zlmAh1c0tvpPyhSDXxSG79u8e4Q==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.18.tgz", + "integrity": "sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==", "dev": true, "license": "MIT", "funding": { @@ -5864,14 +5876,14 @@ } }, "node_modules/@storybook/preset-react-webpack": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.6.15.tgz", - "integrity": "sha512-AY2o3otXHjEr1WIPk+8PelABkk5sBJhPu13BVSWajMOY8W8fraN9CQxrsMFxlG6vTFQg1cDf9kFlCKFHERPiHg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-8.6.18.tgz", + "integrity": "sha512-UkioZsLIyKGQTAdVB3EMx4NyqwIPDRyuDTIQyCwlMcLYCJCs9Ks2ILbM1x1554/iqRIxy8Yv2IBMapK+euCwgg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core-webpack": "8.6.15", - "@storybook/react": "8.6.15", + "@storybook/core-webpack": "8.6.18", + "@storybook/react": "8.6.18", "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", "@types/semver": "^7.3.4", "find-up": "^5.0.0", @@ -5892,7 +5904,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.15" + "storybook": "^8.6.18" }, "peerDependenciesMeta": { "typescript": { @@ -5901,9 +5913,9 @@ } }, "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/components": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.15.tgz", - "integrity": "sha512-+9GVKXPEW8Kl9zvNSTm9+VrJtx/puMZiO7gxCML63nK4aTWJXHQr4t9YUoGammSBM3AV1JglsKm6dBgJEeCoiA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.18.tgz", + "integrity": "sha512-55yViiZzPS/cPBuOeW4QGxGqrusjXVyxuknmbYCIwDtFyyvI/CgbjXRHdxNBaIjz+IlftxvBmmSaOqFG5+/dkA==", "dev": true, "license": "MIT", "funding": { @@ -5914,30 +5926,10 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, - "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/instrumenter": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.15.tgz", - "integrity": "sha512-TvHR/+yyIAOp/1bLulFai2kkhIBtAlBw7J6Jd9DKyInoGhTWNE1G1Y61jD5GWXX29AlwaHfzGUaX5NL1K+FJpg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@storybook/global": "^5.0.0", - "@vitest/utils": "^2.1.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.15" - } - }, "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/manager-api": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.15.tgz", - "integrity": "sha512-ZOFtH821vFcwzECbFYFTKtSVO96Cvwwg45dMh3M/9bZIdN7klsloX7YNKw8OKvwE6XLFLsi2OvsNNcmTW6g88w==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.18.tgz", + "integrity": "sha512-BjIp12gEMgzFkEsgKpDIbZdnSWTZpm2dlws8WiPJCpgJtG+HWSxZ0/Ms30Au9yfwzQEKRSbV/5zpsKMGc2SIJw==", "dev": true, "license": "MIT", "funding": { @@ -5949,9 +5941,9 @@ } }, "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/preview-api": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.15.tgz", - "integrity": "sha512-oqsp8f7QekB9RzpDqOXZQcPPRXXd/mTsnZSdAAQB/pBVqUpC9h/y5hgovbYnJ6DWXcpODbMwH+wbJHZu5lvm+w==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.18.tgz", + "integrity": "sha512-joXRXh3GdVvzhbfIgmix1xs90p8Q/nja7AhEAC2egn5Pl7SKsIYZUCYI6UdrQANb2myg9P552LKXfPect8llKg==", "dev": true, "license": "MIT", "funding": { @@ -5963,18 +5955,18 @@ } }, "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/react": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.15.tgz", - "integrity": "sha512-hdnhlJg+YkpPMOw2hvK7+mhdxAbguA+TFTIAzVV9CeUYoHDIZAsgeKVhRmgZGN20NGjRN5ZcwkplAMJnF9v+6w==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.18.tgz", + "integrity": "sha512-BuLpzMkKtF+UCQCbi+lYVX9cdcAMG86Lu2dDn7UFkPi5HRNFq/zHPSvlz1XDgL0OYMtcqB1aoVzFzcyzUBhhjw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/components": "8.6.15", + "@storybook/components": "8.6.18", "@storybook/global": "^5.0.0", - "@storybook/manager-api": "8.6.15", - "@storybook/preview-api": "8.6.15", - "@storybook/react-dom-shim": "8.6.15", - "@storybook/theming": "8.6.15" + "@storybook/manager-api": "8.6.18", + "@storybook/preview-api": "8.6.18", + "@storybook/react-dom-shim": "8.6.18", + "@storybook/theming": "8.6.18" }, "engines": { "node": ">=18.0.0" @@ -5984,10 +5976,10 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "@storybook/test": "8.6.15", + "@storybook/test": "8.6.18", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.15", + "storybook": "^8.6.18", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { @@ -6000,9 +5992,9 @@ } }, "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/react-dom-shim": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.15.tgz", - "integrity": "sha512-m2trBmmd4iom1qwrp1F109zjRDc0cPaHYhDQxZR4Qqdz8pYevYJTlipDbH/K4NVB6Rn687RT29OoOPfJh6vkFA==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.18.tgz", + "integrity": "sha512-N4xULcAWZQTUv4jy1/d346Tyb4gufuC3UaLCuU/iVSZ1brYF4OW3ANr+096btbMxY8pR/65lmtoqr5CTGwnBvA==", "dev": true, "license": "MIT", "funding": { @@ -6012,38 +6004,13 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^8.6.15" - } - }, - "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/test": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.15.tgz", - "integrity": "sha512-EwquDRUDVvWcZds3T2abmB5wSN/Vattal4YtZ6fpBlIUqONV4o/cOBX39cFfQSUCBrIXIjQ6RmapQCHK/PvBYw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/instrumenter": "8.6.15", - "@testing-library/dom": "10.4.0", - "@testing-library/jest-dom": "6.5.0", - "@testing-library/user-event": "14.5.2", - "@vitest/expect": "2.0.5", - "@vitest/spy": "2.0.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.6.15" + "storybook": "^8.6.18" } }, "node_modules/@storybook/preset-react-webpack/node_modules/@storybook/theming": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.15.tgz", - "integrity": "sha512-dAbL0XOekyT6XsF49R6Etj3WxQ/LpdJDIswUUeHgVJ6/yd2opZOGbPxnwA3zlmAh1c0tvpPyhSDXxSG79u8e4Q==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.18.tgz", + "integrity": "sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==", "dev": true, "license": "MIT", "funding": { @@ -6054,96 +6021,10 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, - "node_modules/@storybook/preset-react-webpack/node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@storybook/preset-react-webpack/node_modules/@testing-library/jest-dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", - "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "chalk": "^3.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.21", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@storybook/preset-react-webpack/node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/preset-react-webpack/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@storybook/preset-react-webpack/node_modules/@testing-library/user-event": { - "version": "14.5.2", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", - "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, "node_modules/@storybook/preset-react-webpack/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7435,9 +7316,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -7445,13 +7326,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -8087,9 +7968,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -8176,9 +8057,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -8211,9 +8092,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -8563,9 +8444,9 @@ } }, "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, "license": "MIT" }, @@ -8702,14 +8583,14 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, "node_modules/axobject-query": { @@ -9030,12 +8911,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz", - "integrity": "sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==", + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", + "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/better-opn": { @@ -9074,9 +8958,9 @@ } }, "node_modules/bn.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", "dev": true, "license": "MIT" }, @@ -9088,9 +8972,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -9257,9 +9141,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -9276,11 +9160,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -9435,9 +9319,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001745", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", - "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", "funding": [ { "type": "opencollective", @@ -9902,9 +9786,9 @@ } }, "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, "license": "MIT" }, @@ -10037,9 +9921,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -10348,7 +10232,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -10573,9 +10456,9 @@ } }, "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, "license": "MIT" }, @@ -10766,9 +10649,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.227", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.227.tgz", - "integrity": "sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA==", + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", "license": "ISC" }, "node_modules/elliptic": { @@ -10788,9 +10671,9 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, "license": "MIT" }, @@ -10896,14 +10779,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -12245,9 +12128,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -12352,9 +12235,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -12365,9 +12248,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -12660,21 +12543,21 @@ "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -15237,13 +15120,17 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -15273,9 +15160,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -15481,9 +15368,9 @@ } }, "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, "license": "MIT" }, @@ -15553,9 +15440,9 @@ "license": "MIT" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -15660,12 +15547,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.11", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.11.tgz", - "integrity": "sha512-L2KPiKmqTDpRdeVDdPjhf43g2/VPe0NCNndq7OKDCgOLWtxe1kbr/zXGIZtYY7kZEAjRf7Bj/mwUFSr+tYC2Yg==", + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", + "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", "license": "MIT", "dependencies": { - "@next/env": "15.5.11", + "@next/env": "15.5.14", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -15678,14 +15565,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.7", - "@next/swc-darwin-x64": "15.5.7", - "@next/swc-linux-arm64-gnu": "15.5.7", - "@next/swc-linux-arm64-musl": "15.5.7", - "@next/swc-linux-x64-gnu": "15.5.7", - "@next/swc-linux-x64-musl": "15.5.7", - "@next/swc-win32-arm64-msvc": "15.5.7", - "@next/swc-win32-x64-msvc": "15.5.7", + "@next/swc-darwin-arm64": "15.5.14", + "@next/swc-darwin-x64": "15.5.14", + "@next/swc-linux-arm64-gnu": "15.5.14", + "@next/swc-linux-arm64-musl": "15.5.14", + "@next/swc-linux-x64-gnu": "15.5.14", + "@next/swc-linux-x64-musl": "15.5.14", + "@next/swc-win32-arm64-msvc": "15.5.14", + "@next/swc-win32-x64-msvc": "15.5.14", "sharp": "^0.34.3" }, "peerDependencies": { @@ -16235,9 +16122,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "license": "MIT" }, "node_modules/normalize-path": { @@ -16781,9 +16668,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -17067,15 +16954,18 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/postcss-loader": { @@ -17427,10 +17317,13 @@ "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/psl": { "version": "1.15.0", @@ -17461,9 +17354,9 @@ } }, "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", - "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, "license": "MIT" }, @@ -17495,9 +17388,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -18519,9 +18412,9 @@ } }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { @@ -18539,9 +18432,9 @@ } }, "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -18585,16 +18478,6 @@ "semver": "bin/semver.js" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -18905,35 +18788,18 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19039,13 +18905,13 @@ } }, "node_modules/storybook": { - "version": "8.6.15", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.15.tgz", - "integrity": "sha512-Ob7DMlwWx8s7dMvcQ3xPc02TvUeralb+xX3oaPRk9wY9Hc6M1IBC/7cEoITkSmRS2v38DHubC+mtEKNc1u2gQg==", + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.18.tgz", + "integrity": "sha512-p8seiSI6FiVY6P3V0pG+5v7c8pDMehMAFRWEhG5XqIBSQszzOjDnW2rNvm3odoLKfo3V3P6Cs6Hv9ILzymULyQ==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/core": "8.6.15" + "@storybook/core": "8.6.18" }, "bin": { "getstorybook": "bin/index.cjs", @@ -19556,9 +19422,9 @@ } }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", "dev": true, "license": "MIT", "engines": { @@ -19570,9 +19436,9 @@ } }, "node_modules/terser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -19589,16 +19455,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -19802,9 +19667,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -20284,9 +20149,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -20507,9 +20372,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { @@ -20531,9 +20396,9 @@ } }, "node_modules/webpack": { - "version": "5.101.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", - "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", "dependencies": { @@ -20543,25 +20408,25 @@ "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.3.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -20621,9 +20486,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { @@ -20650,6 +20515,13 @@ "acorn": "^8.14.0" } }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -21035,9 +20907,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", "engines": { diff --git a/scripts/benchmark_metal.sh b/scripts/benchmark_metal.sh new file mode 100755 index 0000000..9060a3b --- /dev/null +++ b/scripts/benchmark_metal.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Standardized Metal GPU benchmark for Apple Silicon. +# +# Usage: +# ./scripts/benchmark_metal.sh [MODEL_PATH] [ITERATIONS] [TOKENS] +# +# Defaults: +# MODEL_PATH test_models/TinyLlama-1.1B-Chat-v1.0.Q4_K_M.gguf +# ITERATIONS 10 +# TOKENS 100 +# +# Output: +# Prints results to stdout and writes a timestamped JSON file. +# Share the JSON at: https://github.com/ringo380/inferno/issues/7 + +set -euo pipefail + +MODEL="${1:-test_models/TinyLlama-1.1B-Chat-v1.0.Q4_K_M.gguf}" +ITERATIONS="${2:-10}" +TOKENS="${3:-100}" +WARMUP=3 +OUTPUT="benchmark_results_$(date +%Y%m%d_%H%M%S).json" + +if [[ ! -f "$MODEL" ]]; then + echo "Error: model file not found: $MODEL" + echo "Usage: $0 [MODEL_PATH] [ITERATIONS] [TOKENS]" + exit 1 +fi + +echo "=== Inferno Metal GPU Benchmark ===" +echo "Model: $MODEL" +echo "Iterations: $ITERATIONS (+ $WARMUP warmup)" +echo "Tokens: $TOKENS per iteration" +echo "" + +cargo run --release -- bench \ + --model "$MODEL" \ + --iterations "$ITERATIONS" \ + --warmup "$WARMUP" \ + --tokens "$TOKENS" \ + --output-json "$OUTPUT" + +echo "" +echo "JSON results: $OUTPUT" +echo "" +echo "To contribute results for issue #7, share the JSON file at:" +echo " https://github.com/ringo380/inferno/issues/7" +echo "" +echo "Include your chip model (e.g. M2 Pro), GPU core count, and RAM size." diff --git a/src/cli/bench.rs b/src/cli/bench.rs index 572c878..b98fa3f 100644 --- a/src/cli/bench.rs +++ b/src/cli/bench.rs @@ -3,6 +3,7 @@ use crate::config::Config; use crate::models::ModelManager; use anyhow::Result; use clap::Args; +use std::path::PathBuf; use std::time::{Duration, Instant}; use tracing::info; @@ -28,6 +29,29 @@ pub struct BenchArgs { #[arg(long, help = "Enable detailed per-iteration output")] pub verbose: bool, + + #[arg(long, value_name = "FILE", help = "Write results to JSON file for comparison tracking")] + pub output_json: Option, +} + +#[derive(serde::Serialize)] +struct BenchmarkJsonResult { + model: String, + backend: String, + iterations: u32, + max_tokens: u32, + throughput_tokens_per_sec: f64, + mean_latency_ms: f64, + min_latency_ms: f64, + max_latency_ms: f64, + median_latency_ms: f64, + total_tokens: u32, + load_time_ms: u64, + memory_used_gb: Option, + total_memory_gb: Option, + hostname: Option, + os_version: Option, + timestamp: String, } pub async fn execute(args: BenchArgs, config: &Config) -> Result<()> { @@ -172,8 +196,35 @@ pub async fn execute(args: BenchArgs, config: &Config) -> Result<()> { println!("Performance: {}", performance_rating); // Memory usage estimation - if let Ok(memory_info) = get_memory_info() { - println!("Estimated memory usage: {:.1} GB", memory_info.used_gb); + let memory_used_gb = get_memory_info().ok().map(|m| m.used_gb); + if let Some(gb) = memory_used_gb { + println!("Estimated memory usage: {:.1} GB", gb); + } + + // Write JSON results if requested + if let Some(json_path) = &args.output_json { + let hw = get_hardware_info(); + let result = BenchmarkJsonResult { + model: model_info.name.clone(), + backend: backend_type.to_string(), + iterations: args.iterations, + max_tokens: args.tokens, + throughput_tokens_per_sec: total_tokens_per_sec, + mean_latency_ms: mean.as_secs_f64() * 1000.0, + min_latency_ms: min.as_secs_f64() * 1000.0, + max_latency_ms: max.as_secs_f64() * 1000.0, + median_latency_ms: median.as_secs_f64() * 1000.0, + total_tokens, + load_time_ms: load_time.as_millis() as u64, + memory_used_gb, + total_memory_gb: hw.total_memory_gb, + hostname: hw.hostname, + os_version: hw.os_version, + timestamp: chrono::Utc::now().to_rfc3339(), + }; + let json = serde_json::to_string_pretty(&result)?; + std::fs::write(json_path, json)?; + println!("\nResults written to {}", json_path.display()); } Ok(()) @@ -209,6 +260,18 @@ fn validate_args(args: &BenchArgs) -> Result<()> { anyhow::bail!("Warmup iterations must be 100 or less"); } + // Validate output JSON parent directory exists + if let Some(json_path) = &args.output_json { + if let Some(parent) = json_path.parent() { + if !parent.as_os_str().is_empty() && !parent.exists() { + anyhow::bail!( + "Output directory does not exist: {}", + parent.display() + ); + } + } + } + Ok(()) } @@ -240,8 +303,8 @@ fn get_memory_info() -> Result { sys.refresh_memory(); Ok(MemoryInfo { - used_gb: sys.used_memory() as f64 / 1024.0 / 1024.0 / 1024.0, - total_gb: sys.total_memory() as f64 / 1024.0 / 1024.0 / 1024.0, + used_gb: sys.used_memory() as f64 / 1_073_741_824.0, + total_gb: sys.total_memory() as f64 / 1_073_741_824.0, }) } @@ -251,6 +314,23 @@ struct MemoryInfo { total_gb: f64, } +struct HardwareInfo { + total_memory_gb: Option, + hostname: Option, + os_version: Option, +} + +fn get_hardware_info() -> HardwareInfo { + use sysinfo::{System, SystemExt}; + let mut sys = System::new_all(); + sys.refresh_all(); + HardwareInfo { + total_memory_gb: Some(sys.total_memory() as f64 / 1_073_741_824.0), + hostname: sys.host_name(), + os_version: sys.os_version(), + } +} + #[cfg(test)] mod tests { use super::*; @@ -291,6 +371,7 @@ mod tests { warmup: 3, backend: None, verbose: false, + output_json: None, }; let result = validate_args(&args); assert!(result.is_err()); @@ -312,6 +393,7 @@ mod tests { warmup: 3, backend: None, verbose: false, + output_json: None, }; let result = validate_args(&args); assert!(result.is_err()); @@ -333,6 +415,7 @@ mod tests { warmup: 3, backend: None, verbose: false, + output_json: None, }; let result = validate_args(&args); assert!(result.is_err()); @@ -349,6 +432,7 @@ mod tests { warmup: 3, backend: None, verbose: false, + output_json: None, }; let result = validate_args(&args); assert!(result.is_err()); @@ -370,6 +454,7 @@ mod tests { warmup: 3, backend: None, verbose: false, + output_json: None, }; let result = validate_args(&args); assert!(result.is_err()); @@ -386,6 +471,7 @@ mod tests { warmup: 101, backend: None, verbose: false, + output_json: None, }; let result = validate_args(&args); assert!(result.is_err()); @@ -407,6 +493,7 @@ mod tests { warmup: 3, backend: None, verbose: true, + output_json: None, }; let result = validate_args(&args); assert!(result.is_ok()); diff --git a/tests/metal_performance_tests.rs b/tests/metal_performance_tests.rs index 4c62a2e..57e248f 100644 --- a/tests/metal_performance_tests.rs +++ b/tests/metal_performance_tests.rs @@ -103,19 +103,40 @@ mod metal_tests { ); } - /// Performance test: Measure tokens per second for inference - /// This test requires a real model file to run meaningful benchmarks. - /// Set INFERNO_MODELS_DIR and have a valid .gguf model available. + /// Performance test: Measure tokens per second for inference. + /// + /// Collects the full metric set needed for issue #7: + /// throughput, load time, GPU config, and hardware info. + /// + /// Requires a real model file. Set INFERNO_MODELS_DIR and ensure a + /// valid .gguf model is present, then run: + /// + /// cargo test --test metal_performance_tests test_inference_performance -- --ignored + /// + /// To contribute results to issue #7, use scripts/benchmark_metal.sh instead, + /// which writes a structured JSON file you can share. #[tokio::test] - #[ignore = "Requires real model file - run with: cargo test --test metal_performance_tests test_inference_performance -- --ignored"] + #[ignore = "Requires real model file — see doc comment above"] async fn test_inference_performance() { use std::env; + use sysinfo::{System, SystemExt}; + // ── Hardware / environment info ──────────────────────────────────── + let mut sys = System::new_all(); + sys.refresh_all(); + let hostname = sys.host_name().unwrap_or_else(|| "unknown".to_string()); + let total_memory_gb = sys.total_memory() as f64 / 1_073_741_824.0; + + println!("=== Metal GPU Performance Test ==="); + println!("Host: {}", hostname); + println!("Total RAM: {:.1} GB", total_memory_gb); + println!("Platform: macOS (Apple Silicon)"); + + // ── Locate model ──────────────────────────────────────────────────── let models_dir = env::var("INFERNO_MODELS_DIR").unwrap_or_else(|_| "test_models".to_string()); let model_path = PathBuf::from(&models_dir); - // Find first .gguf file in models directory let model_file = std::fs::read_dir(&model_path).ok().and_then(|entries| { entries .filter_map(|e| e.ok()) @@ -139,16 +160,23 @@ mod metal_tests { } }; - println!("Testing with model: {:?}", model_file); + let model_size_bytes = std::fs::metadata(&model_file) + .map(|m| m.len()) + .unwrap_or(0); + let model_size_mb = model_size_bytes as f64 / 1_048_576.0; + println!("Model: {}", model_file.display()); + println!("Model size: {:.0} MB", model_size_mb); - // Create backend with Metal acceleration + // ── Backend config ────────────────────────────────────────────────── let config = BackendConfig::with_metal_acceleration(); + println!("GPU enabled: {}", config.gpu_enabled); + println!("Context size: {}", config.context_size); + println!("Batch size: {}", config.batch_size); + let mut backend = Backend::new(BackendType::Gguf, &config).expect("Failed to create GGUF backend"); - // Create model info let model_metadata = std::fs::metadata(&model_file).ok(); - let model_size = model_metadata.as_ref().map(|m| m.len()).unwrap_or(0); let model_modified = model_metadata .and_then(|m| m.modified().ok()) .map(|t| chrono::DateTime::::from(t)) @@ -162,8 +190,8 @@ mod metal_tests { .to_string(), path: model_file.clone(), file_path: model_file.clone(), - size: model_size, - size_bytes: model_size, + size: model_size_bytes, + size_bytes: model_size_bytes, modified: model_modified, format: "gguf".to_string(), backend_type: "gguf".to_string(), @@ -171,70 +199,99 @@ mod metal_tests { metadata: Default::default(), }; - // Load the model + // ── Load model ────────────────────────────────────────────────────── let load_start = Instant::now(); backend .load_model(&model_info) .await .expect("Failed to load model"); let load_time = load_start.elapsed(); - println!("Model loaded in {:?}", load_time); + println!("\nLoad time: {:?}", load_time); - // Run inference benchmark + // ── Inference benchmark ───────────────────────────────────────────── let params = InferenceParams { max_tokens: 100, - temperature: 0.0, // Deterministic for benchmarking + temperature: 0.0, // Deterministic ..Default::default() }; let prompt = "The quick brown fox"; - let num_runs = 3; - let mut total_tokens = 0u32; - let mut total_time_ms = 0u64; + let num_runs = 5; + let mut run_tokens = Vec::new(); + let mut run_times_ms = Vec::new(); + println!("\nRunning {} inference iterations...", num_runs); for i in 0..num_runs { let start = Instant::now(); let result = backend.infer(prompt, ¶ms).await; let elapsed = start.elapsed(); + let elapsed_ms = elapsed.as_millis() as u64; match result { Ok(output) => { - let tokens = output.split_whitespace().count() as u32; - total_tokens += tokens; - total_time_ms += elapsed.as_millis() as u64; + // Use same heuristic as bench CLI: ~4 chars per token + let tokens = (output.len() as f32 / 4.0).ceil() as u32; + let tps = tokens as f64 / elapsed.as_secs_f64(); + run_tokens.push(tokens); + run_times_ms.push(elapsed_ms); println!( - "Run {}: {} tokens in {:?} ({:.1} tok/s)", + " Run {:2}: {:>5} est. tokens {:>7.1} ms {:>7.1} tok/s", i + 1, tokens, - elapsed, - tokens as f64 / elapsed.as_secs_f64() + elapsed_ms, + tps ); } Err(e) => { - eprintln!("Inference failed on run {}: {}", i + 1, e); + eprintln!(" Run {}: inference failed — {}", i + 1, e); } } } - if total_tokens > 0 && total_time_ms > 0 { - let avg_tokens_per_sec = (total_tokens as f64) / (total_time_ms as f64 / 1000.0); + // ── Summary ───────────────────────────────────────────────────────── + if !run_tokens.is_empty() { + let total_tokens: u32 = run_tokens.iter().sum(); + let total_ms: u64 = run_times_ms.iter().sum(); + let avg_tps = (total_tokens as f64) / (total_ms as f64 / 1000.0); + + let mut sorted_ms = run_times_ms.clone(); + sorted_ms.sort_unstable(); + let min_ms = *sorted_ms.first().unwrap() as f64; + let max_ms = *sorted_ms.last().unwrap() as f64; + let n = sorted_ms.len(); + let median_ms = if n % 2 == 1 { + sorted_ms[n / 2] as f64 + } else { + (sorted_ms[n / 2 - 1] + sorted_ms[n / 2]) as f64 / 2.0 + }; + + println!("\n=== Results ==="); + println!("Throughput: {:.1} tok/s (average)", avg_tps); + println!("Latency min: {:.1} ms", min_ms); + println!("Latency max: {:.1} ms", max_ms); + println!("Latency med: {:.1} ms", median_ms); + println!("Total tokens: {}", total_tokens); + println!("Load time: {:?}", load_time); + println!( + "\nGPU memory: check Activity Monitor > GPU tab during inference" + ); + println!( + "Layer offload: check logs for 'offloaded X/X layers to GPU'" + ); println!( - "\nAverage: {:.1} tokens/sec ({} tokens in {} ms)", - avg_tokens_per_sec, total_tokens, total_time_ms + "\nTo contribute these results to issue #7:\n https://github.com/ringo380/inferno/issues/7" ); - // Performance assertion - adjust threshold based on model size - // For a 7B model on M1 Max, we expect >30 tok/s - // This is a soft assertion - we log the result regardless - if avg_tokens_per_sec < 10.0 { + // Sanity check: warn if GPU doesn't appear to be active + if avg_tps < 10.0 { eprintln!( - "Warning: Performance below 10 tok/s ({:.1}). GPU may not be enabled.", - avg_tokens_per_sec + "\nWARNING: throughput {:.1} tok/s is below 10 tok/s. \ + GPU acceleration may not be active — check logs for GPU offload confirmation.", + avg_tps ); } } - // Cleanup backend.unload_model().await.ok(); } }