{"id":36399884,"url":"https://github.com/lucidfrontier45/silva","last_synced_at":"2026-01-11T16:02:53.784Z","repository":{"id":330214438,"uuid":"1120401665","full_name":"lucidfrontier45/silva","owner":"lucidfrontier45","description":"Tiny inference engine for tree ensemble models in Rust","archived":false,"fork":false,"pushed_at":"2025-12-30T14:54:42.000Z","size":1908,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-03T08:15:48.272Z","etag":null,"topics":["ensemble-model","lightgbm","machine-learning","rust","xgboost"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucidfrontier45.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-12-21T06:09:08.000Z","updated_at":"2025-12-30T14:54:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lucidfrontier45/silva","commit_stats":null,"previous_names":["lucidfrontier45/silva"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lucidfrontier45/silva","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucidfrontier45%2Fsilva","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucidfrontier45%2Fsilva/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucidfrontier45%2Fsilva/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucidfrontier45%2Fsilva/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucidfrontier45","download_url":"https://codeload.github.com/lucidfrontier45/silva/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucidfrontier45%2Fsilva/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28312194,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ensemble-model","lightgbm","machine-learning","rust","xgboost"],"created_at":"2026-01-11T16:02:53.110Z","updated_at":"2026-01-11T16:02:53.779Z","avatar_url":"https://github.com/lucidfrontier45.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"logo.png\" alt=\"logo\" width=\"300\"\u003e\n\n[![crates.io](https://img.shields.io/crates/v/silva)](https://crates.io/crates/silva)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Repository](https://img.shields.io/badge/github-lucidfrontier45/silva-blue)](https://github.com/lucidfrontier45/silva)\n\nSilva is a tiny inference engine for tree ensemble models (a.k.a forest models) in Rust.\n\n## Why Silva?\n\nSilva makes it easier for Rust programs to use pre-trained XGBoost and LightGBM models by providing a lightweight inference engine that avoids runtime dependencies on external machine learning libraries. Key benefits include:\n\n- **Pure Rust**: Entirely written in Rust for high performance, memory safety, and zero-cost abstractions\n- **Simple Codebase**: Minimal, clean implementation that's easy to understand, integrate, and maintain\n- **No External Dependencies**: Parses and runs models from XGBoost and LightGBM without requiring those libraries to be installed or linked\n\n# Supported Formats\n\n## Silva Format\n- Native format using efficient serde serialization\n- Most compact and fastest to load\n\n## XGBoost\n- **Booster Types**: `gbtree` only (gblinear and dart are not supported)\n- **Supported Objectives**: \n  - `reg:squarederror` (regression)\n  - `binary:logistic` (binary classification)\n  - `multi:softmax` (multiclass classification)\n  - `multi:softprob` (multiclass classification)\n- **Note**: Unsupported booster types/objectives will return descriptive errors\n\n## LightGBM\n- All regression and classification models\n- Text format only (binary format not supported)\n- Tree structure only (no linear models)\n- Note: LightGBM incorporates all bias into leaf values (no separate base_score)\n\n# Use this library\n\n```sh\ncargo add silva\n```\n\n# Data Structures\n\n## MultiOutputForest\nA container for multi-output models (e.g., multi-class classification). Holds a vector of `Forest` instances, one per output class. Returns a vector of predictions, one per output.\n\n## Forest\nSingle-output tree ensemble containing:\n- `base_value`: Bias/baseline score added to all predictions\n- `trees`: Vector of decision trees\n\nPrediction formula: `base_value + Σ tree_predictions`\n\n## Tree\nIndividual decision tree represented as:\n- `node_map`: Hash map of node ID → `TreeNode`\n- `root`: Root node ID\n\nTraverses tree from root to leaf based on feature comparisons.\n\n## TreeNode\nSingle node with:\n- `split_index`: Feature index for splitting\n- `split_condition`: Threshold value (NotNan\u003cf64\u003e)\n- `left/right`: Child node IDs (None for leaves)\n- `value`: Leaf value (NotNan\u003cf64\u003e)\n\nLeaves have no children; internal nodes contain split logic.\n\n# Silva Format Example\n\n```json\n{\n  \"forests\": [\n    {\n      \"base_value\": 0.5,\n      \"trees\": [\n        {\n          \"nm\": {\n            \"0\": {\"id\": 0, \"si\": 0, \"sc\": 2.5, \"l\": 1, \"r\": 2, \"v\": 0.0},\n            \"1\": {\"id\": 1, \"si\": 1, \"sc\": 1.5, \"l\": null, \"r\": null, \"v\": 3.0},\n            \"2\": {\"id\": 2, \"si\": 1, \"sc\": 3.5, \"l\": null, \"r\": null, \"v\": 5.0}\n          },\n          \"root\": 0\n        },\n        {\n          \"nm\": {\n            \"0\": {\"id\": 0, \"si\": 0, \"sc\": 5.0, \"l\": 1, \"r\": 2, \"v\": 0.0},\n            \"1\": {\"id\": 1, \"si\": 1, \"sc\": 2.0, \"l\": null, \"r\": null, \"v\": 10.0},\n            \"2\": {\"id\": 2, \"si\": 1, \"sc\": 3.0, \"l\": null, \"r\": null, \"v\": 20.0}\n          },\n          \"root\": 0\n        }\n      ]\n    }\n  ]\n}\n```\n\n## Field Notation\n\n| Abbreviation | Full Name       | Description                                     |\n| ------------ | --------------- | ----------------------------------------------- |\n| `nm`         | node_map        | Hash map mapping node ID to TreeNode            |\n| `si`         | split_index     | Feature index used for splitting at this node   |\n| `sc`         | split_condition | Threshold value for the split comparison        |\n| `l`          | left            | ID of left child node (null for leaves)         |\n| `r`          | right           | ID of right child node (null for leaves)        |\n| `v`          | value           | Leaf prediction value (only used in leaf nodes) |\n\n## Structure Hierarchy\n\n```\nMultiOutputForest\n└── forests: Forest[]\n    ├── base_value: f64 (baseline score)\n    ├── trees: Tree[]\n    │   ├── nm: {node_id: TreeNode}\n    │   │   ├── id: node ID\n    │   │   ├── si: feature index to split on\n    │   │   ├── sc: split threshold\n    │   │   ├── l: left child ID (or null)\n    │   │   ├── r: right child ID (or null)\n    │   │   └── v: leaf value\n    │   └── root: ID of the root node\n```\n\n**Prediction Flow**: Start at root → compare feature[si] with sc → follow l or r → repeat until leaf → sum all tree values → add base_value\n\n# Usage Examples\n\n## Basic Prediction\n\nThe predict methods work with feature vectors (`\u0026[f64]`) and return prediction values.\n\n### Single Tree Prediction\n```rust\nuse silva::Tree;\n\nlet tree = Tree::new(node_map, root_id);\nlet prediction = tree.predict(\u0026[1.5, 2.3, 0.8]); // returns NotNan\u003cf64\u003e\n```\n\n### Forest (Single Output)\n```rust\nuse silva::Forest;\n\nlet forest = Forest::new(base_value, trees);\nlet prediction = forest.predict(\u0026[1.5, 2.3, 0.8]); // returns NotNan\u003cf64\u003e\n```\n\n### Multi-Output Forest\n```rust\nuse silva::MultiOutputForest;\n\nlet model = MultiOutputForest::new(forests);\nlet predictions = model.predict(\u0026[1.5, 2.3, 0.8]); // returns Vec\u003cNotNan\u003cf64\u003e\u003e\n```\n\n## Complete Workflow Example\n\n```rust\nuse silva::MultiOutputForest;\n\nfn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Load model from file\n    let model = MultiOutputForest::from_file(\"model.json\")?;\n    \n    // Prepare feature data\n    let features = vec![vec![1.5, 2.3, 0.8], vec![0.5, 1.2, 3.4]];\n    \n    // Make predictions\n    for x in \u0026features {\n        let prediction = model.predict(x);\n        println!(\"Predictions: {:?}\", prediction);\n    }\n    \n    Ok(())\n}\n```\n\n## Understanding Predictions\n\nThe `predict` methods return **raw values** that may require post-processing depending on the model type and objective:\n\n### Classification Models\n\nFor binary classification using `binary:logistic`, apply sigmoid to the raw prediction:\n```rust\nlet raw = forest.predict(\u0026features);\nlet probability = 1.0 / (1.0 + (-raw).exp()); // sigmoid\n```\n\nFor multiclass classification using `multi:softmax` or `multi:softprob`, apply softmax to the predictions:\n```rust\nlet raw_values = model.predict(\u0026features);\nlet exp_values: Vec\u003cf64\u003e = raw_values.iter().map(|\u0026v| v.exp()).collect();\nlet sum: f64 = exp_values.iter().sum();\nlet probabilities: Vec\u003cf64\u003e = exp_values.iter().map(|\u0026v| v / sum).collect();\n```\n\n### Regression Models\n\nFor Poisson regression objectives, apply exponential to the raw prediction:\n```rust\nlet raw = forest.predict(\u0026features);\nlet count_prediction = raw.exp();\n```\n\nFor standard regression (e.g., `reg:squarederror`), the raw value can be used directly.\n\n### LightGBM Note\n\nLightGBM models incorporate bias into leaf values, so no separate `base_score` adjustment is needed for predictions.\n\nFor more examples, see `examples/prediction.rs`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucidfrontier45%2Fsilva","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucidfrontier45%2Fsilva","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucidfrontier45%2Fsilva/lists"}