herosal-capacitymodel

Crates.ioherosal-capacitymodel
lib.rsherosal-capacitymodel
version0.1.1
created_at2025-12-13 11:20:45.632876+00
updated_at2025-12-14 13:35:52.906114+00
descriptionCapacity Model - Node specification and capacity planning for GeoMind/Mycelium
homepage
repositoryhttps://github.com/threefoldtech/sal
max_upload_size
id1982826
size245,030
kristof de spiegeleer (despiegk)

documentation

README

Capacity Model (sal-capacitymodel)

Node specification and capacity planning for GeoMind/Mycelium.

Installation

Add this to your Cargo.toml:

[dependencies]
sal-capacitymodel = { git = "https://forge.ourworld.tf/geomind_research/herolib_rust.git", package = "sal-capacitymodel" }

Then in your Rust code:

use sal_capacitymodel::{NodeSpec, HardwareSpec, CapacitySpec, AgentSpec, SliceSpec, PricingSpec};

Overview

This crate provides data structures for defining node specifications including hardware, capacity, agents, slices, and pricing information. It supports both TOML and HeroScript serialization formats.

Usage

Creating a NodeSpec

use sal_capacitymodel::*;

let node = NodeSpec {
    name: "ryzen_ai_max_395".into(),
    hardware: HardwareSpec {
        hardware_cost_usd: 4200.0,
        power_consumption_watts: 180.0,
        rack_units: 2.0,
    },
    capacity: CapacitySpec {
        memory_gb: 128,
        vcpu: 32,
        ssd_gb: 2000,
        hdd_gb: 0,
        nr_gpu: 0,
        passmark: 50000,
        ai_tps_performance_70b: 85000,
    },
    agents: AgentSpec {
        max_agents: 16,
        agent_type: "pro".into(),
    },
    slices: SliceSpec { nr_slices: 8 },
    pricing: PricingSpec {
        node_price_per_slice_per_month: 0.12,
        agent_price_per_month: 0.03,
        hdd_price_per_gb_per_month: 0.002,
    },
};

TOML Serialization

Export to TOML

let toml_str = node.to_toml().unwrap();
println!("{}", toml_str);

Output:

name = "ryzen_ai_max_395"

[hardware]
hardware_cost_usd = 4200.0
power_consumption_watts = 180.0
rack_units = 2.0

[capacity]
memory_gb = 128
vcpu = 32
ssd_gb = 2000
hdd_gb = 0
nr_gpu = 0
passmark = 50000
ai_tps_performance_70b = 85000

[agents]
max_agents = 16
agent_type = "pro"

[slices]
nr_slices = 8

[pricing]
node_price_per_slice_per_month = 0.12
agent_price_per_month = 0.03
hdd_price_per_gb_per_month = 0.002

Import from TOML

use sal_capacitymodel::NodeSpec;

let toml_str = r#"
name = "parsed_node"

[hardware]
hardware_cost_usd = 5000.0
power_consumption_watts = 200.0
rack_units = 2.0

[capacity]
memory_gb = 256
vcpu = 64
ssd_gb = 4000
hdd_gb = 8000
nr_gpu = 2
passmark = 100000
ai_tps_performance_70b = 150000

[agents]
max_agents = 32
agent_type = "enterprise"

[slices]
nr_slices = 16

[pricing]
node_price_per_slice_per_month = 0.25
agent_price_per_month = 0.05
hdd_price_per_gb_per_month = 0.003
"#;

let node = NodeSpec::from_toml(toml_str).unwrap();
assert_eq!(node.name, "parsed_node");
assert_eq!(node.capacity.memory_gb, 256);

HeroScript Integration

Parse from HeroScript

use sal_capacitymodel::NodeSpec;

let script = r#"
    !!nodespec.define
        name: 'ryzen_ai_max_395'
        hardware_cost_usd: 4200.0
        power_consumption_watts: 180.0
        rack_units: 2.0
        memory_gb: 128
        vcpu: 32
        ssd_gb: 2000
        hdd_gb: 0
        nr_gpu: 0
        passmark: 50000
        ai_tps_performance_70b: 85000
        max_agents: 16
        agent_type: 'pro'
        nr_slices: 8
        node_price_per_slice_per_month: 0.12
        agent_price_per_month: 0.03
        hdd_price_per_gb_per_month: 0.002
"#;

let nodes = NodeSpec::from_heroscript(script).unwrap();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].name, "ryzen_ai_max_395");

Multiple Nodes in HeroScript

let script = r#"
    !!nodespec.define
        name: 'node_small'
        memory_gb: 64
        vcpu: 16
        nr_slices: 4

    !!nodespec.define
        name: 'node_large'
        memory_gb: 256
        vcpu: 64
        nr_slices: 16
"#;

let nodes = NodeSpec::from_heroscript(script).unwrap();
assert_eq!(nodes.len(), 2);

Export to HeroScript

let heroscript = node.to_heroscript();
println!("{}", heroscript);

Output:

!!nodespec.define
    name: 'ryzen_ai_max_395'
    hardware_cost_usd: 4200
    power_consumption_watts: 180
    rack_units: 2
    memory_gb: 128
    vcpu: 32
    ssd_gb: 2000
    hdd_gb: 0
    nr_gpu: 0
    passmark: 50000
    ai_tps_performance_70b: 85000
    max_agents: 16
    agent_type: 'pro'
    nr_slices: 8
    node_price_per_slice_per_month: 0.12
    agent_price_per_month: 0.03
    hdd_price_per_gb_per_month: 0.002

Data Structures

NodeSpec

The main structure containing all node specifications:

Field Type Description
name String Unique identifier (lowercase, no spaces, ASCII)
hardware HardwareSpec Physical hardware specifications
capacity CapacitySpec Compute and storage capacity
agents AgentSpec Agent configuration
slices SliceSpec Slice configuration
pricing PricingSpec Pricing information

HardwareSpec

Field Type Description
hardware_cost_usd f32 One-time CAPEX cost in USD
power_consumption_watts f32 Average power draw in watts
rack_units f32 Number of rack units (U)

CapacitySpec

Field Type Description
memory_gb u32 Total RAM in GB
vcpu u32 Total virtual CPUs (logical cores)
ssd_gb u32 SSD storage in GB
hdd_gb u32 HDD storage in GB
nr_gpu u32 Number of GPUs
passmark u32 CPU benchmark score (PassMark)
ai_tps_performance_70b u32 AI performance for 70B model (tokens/sec)

AgentSpec

Field Type Description
max_agents u32 Maximum agents allowed
agent_type String Type of agent (lowercase snake_case)

SliceSpec

Field Type Description
nr_slices u32 Number of slices provided

PricingSpec

Field Type Description
node_price_per_slice_per_month f64 Price per slice per month (USD)
agent_price_per_month f64 Price per agent per month (USD)
hdd_price_per_gb_per_month f64 Price per GB HDD per month (USD)

HeroScript Parameters Reference

When using !!nodespec.define, the following parameters are available:

Parameter Required Default Description
name Yes - Node identifier
hardware_cost_usd No 0.0 Hardware cost
power_consumption_watts No 0.0 Power consumption
rack_units No 1.0 Rack units
memory_gb No 0 RAM in GB
vcpu No 0 Virtual CPUs
ssd_gb No 0 SSD storage
hdd_gb No 0 HDD storage
nr_gpu No 0 GPU count
passmark No 0 CPU benchmark
ai_tps_performance_70b No 0 AI performance
max_agents No 0 Max agents
agent_type No "standard" Agent type
nr_slices No 1 Slice count
node_price_per_slice_per_month No 0.0 Slice price
agent_price_per_month No 0.0 Agent price
hdd_price_per_gb_per_month No 0.0 HDD price

Slice Calculation Rules

NodeSpec includes automatic slice calculation based on hardware:

  • GPU nodes: nr_slices = nr_gpu (each GPU gets its own slice)
  • Non-GPU nodes: nr_slices = memory_gb / 4 (4 GB per slice)
let mut node = NodeSpec::default();
node.capacity.memory_gb = 128;
node.capacity.nr_gpu = 0;
node.auto_configure_slices();
assert_eq!(node.slices.nr_slices, 32); // 128 / 4 = 32 slices

Pricing Methods

// Total monthly revenue for all slices
let monthly = node.total_monthly_revenue();

// Slice pricing (hourly)
let hourly = node.slice_price_per_hour();           // 2x multiplier for on-demand
let committed = node.slice_price_per_hour_committed(); // No multiplier

// Agent pricing (hourly)
let agent_hourly = node.agent_price_per_hour();     // 2x multiplier
let agent_committed = node.agent_price_per_hour_committed();

Preset Factory Methods

Quick creation of common node configurations:

// Standard compute node (64GB/16vCPU, 16 slices)
let compute = NodeSpec::preset_compute();

// AI workstation (128GB/32vCPU/1GPU, 1 slice)
let workstation = NodeSpec::preset_workstation();

// Quad-GPU server (512GB/128vCPU/4GPU, 4 slices)
let server = NodeSpec::preset_server_quatro();

NodeCollection

Manage multiple nodes together:

use sal_capacitymodel::{NodeCollection, NodeSpec};

let mut collection = NodeCollection::new();

// Add nodes
collection.add(NodeSpec::preset_compute());
collection.add(NodeSpec::preset_workstation());

// Query
let compute = collection.get("compute").unwrap();
assert!(collection.contains("compute"));

// Aggregates
let total_revenue = collection.total_monthly_revenue();
let total_slices = collection.total_slices();
let total_power = collection.total_power_consumption();
let total_cost = collection.total_hardware_cost();

// Serialization
let toml = collection.to_toml().unwrap();
let parsed = NodeCollection::from_toml(&toml).unwrap();

Rhai Scripting

The crate includes full Rhai scripting support with a fluent builder pattern for clean, readable node configuration.

Register the Module

use rhai::Engine;
use sal_capacitymodel::register_capacitymodel_module;

let mut engine = Engine::new();
register_capacitymodel_module(&mut engine).unwrap();

Builder Pattern (Recommended)

The builder pattern provides a clean, fluent API for creating nodes:

let node = nodespec_builder()
    .name("my_server")
    .hardware()
        .cost(5000.0)
        .power(200.0)
        .rack_units(2.0)
        .done()
    .capacity()
        .memory(128)
        .vcpu(32)
        .ssd(2000)
        .gpu(2)
        .done()
    .agents()
        .max(16)
        .agent_type("enterprise")
        .done()
    .pricing()
        .slice_monthly(50.0)
        .agent_monthly(20.0)
        .done()
    .auto_slices()
    .build();

Builder Functions Reference

NodeSpecBuilder

  • nodespec_builder() - Create new builder
  • .name(str) - Set node name
  • .hardware() - Enter hardware configuration
  • .capacity() - Enter capacity configuration
  • .agents() - Enter agent configuration
  • .pricing() - Enter pricing configuration
  • .slices() - Enter slice configuration
  • .auto_slices() - Auto-calculate slice count
  • .build() - Build and return NodeSpec

HardwareBuilder (via .hardware())

  • .cost(usd) - Hardware cost in USD
  • .power(watts) - Power consumption
  • .rack_units(units) - Rack units
  • .done() - Return to NodeSpecBuilder

CapacityBuilder (via .capacity())

  • .memory(gb) - RAM in GB
  • .vcpu(count) - Virtual CPUs
  • .ssd(gb) - SSD storage in GB
  • .hdd(gb) - HDD storage in GB
  • .gpu(count) - Number of GPUs
  • .passmark(score) - CPU benchmark score
  • .ai_performance(tps) - AI tokens/sec benchmark
  • .done() - Return to NodeSpecBuilder

AgentBuilder (via .agents())

  • .max(count) - Maximum agents
  • .agent_type(str) - Agent type name
  • .done() - Return to NodeSpecBuilder

PricingBuilder (via .pricing())

  • .slice_monthly(usd) - Price per slice per month
  • .agent_monthly(usd) - Price per agent per month
  • .hdd_monthly(usd_per_gb) - HDD price per GB per month
  • .done() - Return to NodeSpecBuilder

SliceBuilder (via .slices())

  • .count(n) - Manual slice count
  • .done() - Return to NodeSpecBuilder

Rhai Example: Builder Pattern

// Build a compute node
let compute = nodespec_builder()
    .name("compute_node")
    .hardware()
        .cost(2000.0)
        .power(150.0)
        .done()
    .capacity()
        .memory(64)
        .vcpu(16)
        .ssd(500)
        .done()
    .pricing()
        .slice_monthly(10.0)
        .done()
    .auto_slices()
    .build();

print("Node: " + compute.name);
print("Slices: " + compute.nr_slices);  // 64/4 = 16
print("Revenue: $" + total_monthly_revenue(compute));

Rhai Example: GPU Server with Builder

let gpu_server = nodespec_builder()
    .name("AI Inference Server")
    .hardware()
        .cost(50000.0)
        .power(2000.0)
        .rack_units(4.0)
        .done()
    .capacity()
        .memory(512)
        .vcpu(128)
        .ssd(4000)
        .gpu(4)
        .passmark(200000)
        .ai_performance(400000)
        .done()
    .agents()
        .max(64)
        .agent_type("inference")
        .done()
    .pricing()
        .slice_monthly(500.0)
        .agent_monthly(100.0)
        .done()
    .auto_slices()  // Will set 4 slices (1 per GPU)
    .build();

let slice = slice_capacity(gpu_server);
print("Per slice: " + slice.memory_gb + " GB, " + slice.gpu + " GPU");

Rhai Example: Building a Datacenter

let dc = node_collection_new();

// Add compute nodes
let i = 0;
while i < 10 {
    let node = nodespec_builder()
        .name("compute_" + i)
        .hardware().cost(3000.0).power(200.0).done()
        .capacity().memory(128).vcpu(32).ssd(1000).done()
        .pricing().slice_monthly(12.0).done()
        .auto_slices()
        .build();
    add(dc, node);
    i += 1;
}

// Add GPU servers
i = 0;
while i < 3 {
    let node = nodespec_builder()
        .name("gpu_" + i)
        .hardware().cost(50000.0).power(2000.0).done()
        .capacity().memory(512).vcpu(128).gpu(4).done()
        .pricing().slice_monthly(500.0).done()
        .auto_slices()
        .build();
    add(dc, node);
    i += 1;
}

// Datacenter stats
print("Total nodes: " + len(dc));
print("Total slices: " + total_slices(dc));
print("Monthly revenue: $" + total_monthly_revenue(dc));
print("Hardware cost: $" + total_hardware_cost(dc));

Legacy Functions Reference

These functions are still available for compatibility:

NodeSpec Constructors

  • nodespec_new() - Create empty NodeSpec
  • nodespec_from_toml(toml_str) - Parse from TOML
  • nodespec_from_heroscript(script) - Parse from HeroScript (returns array)
  • nodespec_preset_compute() - Compute node preset
  • nodespec_preset_workstation() - Workstation preset
  • nodespec_preset_server_quatro() - Quad-GPU server preset

NodeSpec Methods

  • to_toml(node) - Export to TOML
  • to_heroscript(node) - Export to HeroScript
  • calculate_nr_slices(node) - Calculate optimal slices
  • auto_configure_slices(node) - Auto-set nr_slices
  • slices_fit(node) - Check if slices fit capacity
  • slice_capacity(node) - Get per-slice capacity
  • total_monthly_revenue(node) - Get monthly revenue
  • slice_price_per_hour(node) - On-demand hourly price
  • slice_price_per_hour_committed(node) - Committed hourly price
  • agent_price_per_hour(node) - Agent on-demand price
  • agent_price_per_hour_committed(node) - Agent committed price
  • normalize(node) - Normalize identifiers
  • validate(node) - Validate configuration
  • summary(node) - Human-readable summary

NodeSpec Properties (get/set)

  • name, nr_slices, memory_gb, vcpu, ssd_gb, hdd_gb, nr_gpu
  • max_agents, agent_type, hardware_cost_usd, power_consumption_watts
  • node_price_per_slice_per_month, agent_price_per_month

SliceCapacity Properties (read-only)

  • memory_gb, vcpu, ssd_gb, hdd_gb, gpu

NodeCollection Functions

  • node_collection_new() - Create empty collection
  • node_collection_from_toml(toml_str) - Parse from TOML
  • add(collection, node) - Add node
  • get(collection, name) - Get node by name
  • remove(collection, name) - Remove node
  • contains(collection, name) - Check if exists
  • len(collection) - Get count
  • is_empty(collection) - Check if empty
  • names(collection) - Get all names (array)
  • to_toml(collection) - Export to TOML
  • total_monthly_revenue(collection) - Sum of revenues
  • total_hardware_cost(collection) - Sum of hardware costs
  • total_power_consumption(collection) - Sum of power
  • total_slices(collection) - Sum of slices
  • summary(collection) - Human-readable summary

Rhai Example: HeroScript Integration

let heroscript = `
!!nodespec.define
    name: 'api_server'
    memory_gb: 128
    vcpu: 32
    ssd_gb: 2000
    nr_slices: 32
    node_price_per_slice_per_month: 15.0
`;

let nodes = nodespec_from_heroscript(heroscript);
for node in nodes {
    print("Loaded: " + node.name);
    print(summary(node));
}

Example Scripts

See examples/rhai/ for complete examples:

  1. 01_basic_nodespec.rhai - Basic NodeSpec creation and usage
  2. 02_custom_nodespec.rhai - Creating custom configurations
  3. 03_node_collection.rhai - Working with NodeCollection
  4. 04_datacenter_planning.rhai - Realistic datacenter planning
  5. 05_heroscript_integration.rhai - HeroScript parsing and export
  6. 06_pricing_comparison.rhai - Pricing analysis and comparison

Testing

cargo test -p sal-capacitymodel
Commit count: 0

cargo fmt