2026-06-23 20:10:41 +00:00
|
|
|
use std::collections::{BTreeMap, HashSet};
|
|
|
|
|
use std::io::{IsTerminal, Write};
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
use crate::config::{ENV_ASSUME_YES, truthy_env};
|
|
|
|
|
use crate::monitor::wait_for_running_with_policy;
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
use crate::pricing::CostModel;
|
2026-06-23 20:10:41 +00:00
|
|
|
use crate::provision::create_instance;
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
use crate::search::{plan_distinct_host_first_wave, select_offer_pool_with_policy};
|
2026-06-23 20:10:41 +00:00
|
|
|
use crate::teardown::{destroy_instance_with_retry, rollback};
|
|
|
|
|
use crate::types::{
|
|
|
|
|
CreateInstanceRequest, InstanceInfo, Offer, ProvisionRequest, ProvisionedFleet,
|
|
|
|
|
ProvisionedInstance,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Print the planned lease + hourly cost and, on TTY, require y/N confirmation.
|
|
|
|
|
pub fn confirm_lease(pool: &[Offer], num_instances: u32, cost: &CostModel) -> Result<(), String> {
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
let picks = plan_distinct_host_first_wave(pool, num_instances, &[], &[]);
|
2026-06-23 20:10:41 +00:00
|
|
|
let total_dph: f64 = picks.iter().map(|o| o.dph_total).sum();
|
|
|
|
|
let total_eff: f64 = picks.iter().map(|o| cost.effective_price(o)).sum();
|
|
|
|
|
|
|
|
|
|
eprintln!("vastai: lease plan — {num_instances} instance(s), cheapest on distinct hosts:");
|
|
|
|
|
for (i, o) in picks.iter().enumerate() {
|
|
|
|
|
eprintln!(
|
|
|
|
|
" node {i} {:<14} {:>8} ${:.3}/hr [{}] host {}",
|
|
|
|
|
o.gpu_name,
|
|
|
|
|
o.gpu_ram
|
|
|
|
|
.map(|r| format!("{:.0}MB", r))
|
|
|
|
|
.unwrap_or_else(|| "?MB".into()),
|
|
|
|
|
o.dph_total,
|
|
|
|
|
o.geolocation.as_deref().unwrap_or("?"),
|
|
|
|
|
o.host_id
|
|
|
|
|
.map(|h| h.to_string())
|
|
|
|
|
.unwrap_or_else(|| "?".into()),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if picks.len() < num_instances as usize {
|
|
|
|
|
eprintln!(
|
|
|
|
|
" WARNING: only {} distinct-host offer(s) available for {num_instances} instance(s)",
|
|
|
|
|
picks.len(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
let eff_note = if (total_eff - total_dph).abs() > 1e-6 {
|
|
|
|
|
format!(" (image-pull priced in: ${total_eff:.3}/hr eff)")
|
|
|
|
|
} else {
|
|
|
|
|
String::new()
|
|
|
|
|
};
|
|
|
|
|
eprintln!(
|
|
|
|
|
" TOTAL ${total_dph:.3}/hr (~${:.2}/day){eff_note}",
|
|
|
|
|
total_dph * 24.0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if truthy_env(ENV_ASSUME_YES) {
|
|
|
|
|
eprintln!("vastai: {ENV_ASSUME_YES} set — proceeding without confirmation");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
if !std::io::stdin().is_terminal() {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"vastai: stdin is not a TTY — proceeding without interactive confirmation \
|
|
|
|
|
(set {ENV_ASSUME_YES}=1 to silence this)"
|
|
|
|
|
);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eprint!("Proceed with renting these {num_instances} instance(s)? [y/N]: ");
|
|
|
|
|
let _ = std::io::stderr().flush();
|
|
|
|
|
let mut line = String::new();
|
|
|
|
|
std::io::stdin()
|
|
|
|
|
.read_line(&mut line)
|
|
|
|
|
.map_err(|e| format!("failed to read lease confirmation: {e}"))?;
|
|
|
|
|
let ans = line.trim().to_ascii_lowercase();
|
|
|
|
|
if ans == "y" || ans == "yes" {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err("operator declined the lease (cost not confirmed); no instances were created".into())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn next_eligible_offer<'a>(
|
|
|
|
|
pool: &'a [Offer],
|
|
|
|
|
tried_offer_ids: &[u64],
|
|
|
|
|
used_host_ids: &HashSet<u64>,
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
failed_host_ids: &HashSet<u64>,
|
|
|
|
|
preferred_offer_id: Option<u64>,
|
2026-06-23 20:10:41 +00:00
|
|
|
) -> Option<&'a Offer> {
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
if let Some(offer_id) = preferred_offer_id {
|
|
|
|
|
if let Some(offer) = pool.iter().find(|o| o.id == offer_id) {
|
|
|
|
|
if !tried_offer_ids.contains(&offer.id)
|
|
|
|
|
&& offer.host_id.is_none_or(|h| !failed_host_ids.contains(&h))
|
|
|
|
|
{
|
|
|
|
|
return Some(offer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 20:10:41 +00:00
|
|
|
pool.iter().find(|o| {
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
!tried_offer_ids.contains(&o.id)
|
|
|
|
|
&& o.host_id
|
|
|
|
|
.is_none_or(|h| !used_host_ids.contains(&h) && !failed_host_ids.contains(&h))
|
2026-06-23 20:10:41 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn env_for_index(req: &ProvisionRequest, index: u32) -> BTreeMap<String, String> {
|
|
|
|
|
let mut env = req.env.clone();
|
|
|
|
|
if let Some(extra) = req.per_instance_env.get(index as usize) {
|
|
|
|
|
env.extend(extra.clone());
|
|
|
|
|
}
|
|
|
|
|
env
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn provision_one(
|
|
|
|
|
client: &reqwest::Client,
|
|
|
|
|
base_url: &str,
|
|
|
|
|
api_key: &str,
|
|
|
|
|
req: &ProvisionRequest,
|
|
|
|
|
pool: &[Offer],
|
|
|
|
|
index: u32,
|
|
|
|
|
tried_offer_ids: &mut Vec<u64>,
|
|
|
|
|
used_host_ids: &mut HashSet<u64>,
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
failed_host_ids: &mut HashSet<u64>,
|
|
|
|
|
preferred_offer_id: Option<u64>,
|
2026-06-23 20:10:41 +00:00
|
|
|
) -> Result<ProvisionedInstance, String> {
|
2026-07-07 10:40:02 +00:00
|
|
|
let mut attempt = 1_u64;
|
|
|
|
|
loop {
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
let offer = match next_eligible_offer(
|
|
|
|
|
pool,
|
|
|
|
|
tried_offer_ids,
|
|
|
|
|
used_host_ids,
|
|
|
|
|
failed_host_ids,
|
|
|
|
|
preferred_offer_id.filter(|offer_id| !tried_offer_ids.contains(offer_id)),
|
|
|
|
|
) {
|
2026-06-23 20:10:41 +00:00
|
|
|
Some(o) => o.clone(),
|
|
|
|
|
None => {
|
2026-07-07 10:40:02 +00:00
|
|
|
return Err(format!(
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
"pool exhausted for index {index} (no untried offer outside failed hosts)"
|
2026-06-23 20:10:41 +00:00
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
tried_offer_ids.push(offer.id);
|
|
|
|
|
let cost = CostModel::from_policy(&req.selection);
|
|
|
|
|
eprintln!(
|
|
|
|
|
"lease_chain: index {index} → offer {} — {} {} @ ${:.3}/hr [{}] host {} eff ${:.3}/hr",
|
|
|
|
|
offer.id,
|
|
|
|
|
offer.gpu_name,
|
|
|
|
|
offer
|
|
|
|
|
.gpu_ram
|
|
|
|
|
.map(|r| format!("{:.0}MB", r))
|
|
|
|
|
.unwrap_or_else(|| "?MB".into()),
|
|
|
|
|
offer.dph_total,
|
|
|
|
|
offer.geolocation.as_deref().unwrap_or("?"),
|
|
|
|
|
offer
|
|
|
|
|
.host_id
|
|
|
|
|
.map(|h| h.to_string())
|
|
|
|
|
.unwrap_or_else(|| "?".into()),
|
|
|
|
|
cost.effective_price(&offer),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let create = CreateInstanceRequest {
|
|
|
|
|
offer_id: offer.id,
|
|
|
|
|
image: req.image.clone(),
|
|
|
|
|
disk_gb: req.disk_gb,
|
|
|
|
|
label: req.label.clone(),
|
|
|
|
|
env: env_for_index(req, index),
|
|
|
|
|
onstart: req.onstart.clone(),
|
|
|
|
|
};
|
|
|
|
|
match create_instance(client, base_url, api_key, &create).await {
|
|
|
|
|
Ok(info) => {
|
|
|
|
|
if let Some(h) = offer.host_id {
|
|
|
|
|
used_host_ids.insert(h);
|
|
|
|
|
}
|
|
|
|
|
return Ok(ProvisionedInstance {
|
|
|
|
|
index,
|
|
|
|
|
contract_id: info.contract_id,
|
|
|
|
|
offer_id: offer.id,
|
|
|
|
|
host_id: offer.host_id,
|
|
|
|
|
gpu_name: offer.gpu_name,
|
|
|
|
|
gpu_ram: offer.gpu_ram,
|
|
|
|
|
dph_total: offer.dph_total,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!(
|
2026-07-07 10:40:02 +00:00
|
|
|
"lease_chain: index {index} create on offer {} failed (attempt {attempt}): {e}",
|
|
|
|
|
offer.id,
|
2026-06-23 20:10:41 +00:00
|
|
|
);
|
|
|
|
|
let is_429 = e.contains("429") || e.contains("Too Many Requests");
|
2026-07-07 10:40:02 +00:00
|
|
|
let backoff = if is_429 {
|
|
|
|
|
std::cmp::min(
|
|
|
|
|
Duration::from_millis(2_000_u64.saturating_mul(attempt)),
|
|
|
|
|
Duration::from_secs(30),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
Duration::from_millis(400)
|
|
|
|
|
};
|
|
|
|
|
tokio::time::sleep(backoff).await;
|
|
|
|
|
attempt = attempt.saturating_add(1);
|
2026-06-23 20:10:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn as_instance_infos(instances: &[ProvisionedInstance]) -> Vec<InstanceInfo> {
|
|
|
|
|
instances
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|i| InstanceInfo {
|
|
|
|
|
contract_id: i.contract_id,
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Rent, monitor, replace, and roll back an N-instance fleet.
|
|
|
|
|
pub async fn provision_fleet(
|
|
|
|
|
client: &reqwest::Client,
|
|
|
|
|
base_url: &str,
|
|
|
|
|
api_key: &str,
|
|
|
|
|
req: ProvisionRequest,
|
|
|
|
|
) -> Result<ProvisionedFleet, String> {
|
|
|
|
|
let pool = select_offer_pool_with_policy(client, base_url, api_key, &req.selection, req.count)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("lease_chain: {e}"))?;
|
|
|
|
|
|
|
|
|
|
if req.confirm_lease {
|
|
|
|
|
confirm_lease(&pool, req.count, &CostModel::from_policy(&req.selection))?;
|
|
|
|
|
}
|
|
|
|
|
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
let first_wave =
|
|
|
|
|
plan_distinct_host_first_wave(&pool, req.count, &req.selection.blacklist_hosts, &[]);
|
2026-06-23 20:10:41 +00:00
|
|
|
let mut tried_offer_ids = Vec::new();
|
|
|
|
|
let mut created: Vec<ProvisionedInstance> = Vec::with_capacity(req.count as usize);
|
|
|
|
|
let mut used_host_ids = HashSet::new();
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
let mut failed_host_ids = HashSet::new();
|
2026-06-23 20:10:41 +00:00
|
|
|
for index in 0..req.count {
|
|
|
|
|
match provision_one(
|
|
|
|
|
client,
|
|
|
|
|
base_url,
|
|
|
|
|
api_key,
|
|
|
|
|
&req,
|
|
|
|
|
&pool,
|
|
|
|
|
index,
|
|
|
|
|
&mut tried_offer_ids,
|
|
|
|
|
&mut used_host_ids,
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
&mut failed_host_ids,
|
|
|
|
|
req.preferred_offer_id
|
|
|
|
|
.filter(|_| req.count == 1)
|
|
|
|
|
.or_else(|| first_wave.get(index as usize).map(|offer| offer.id)),
|
2026-06-23 20:10:41 +00:00
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
Ok(info) => created.push(info),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
rollback(client, base_url, api_key, &as_instance_infos(&created)).await;
|
|
|
|
|
return Err(format!("lease_chain: {e}"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if index + 1 < req.count {
|
|
|
|
|
tokio::time::sleep(req.lifecycle.lease_pace).await;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for index in 0..req.count {
|
|
|
|
|
let idx = index as usize;
|
|
|
|
|
loop {
|
|
|
|
|
let cid = created[idx].contract_id;
|
|
|
|
|
match wait_for_running_with_policy(client, base_url, api_key, cid, &req.lifecycle).await
|
|
|
|
|
{
|
|
|
|
|
Ok(_) => break,
|
|
|
|
|
Err(e) => {
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
if let Some(host_id) = created[idx].host_id {
|
|
|
|
|
failed_host_ids.insert(host_id);
|
|
|
|
|
}
|
2026-06-23 20:10:41 +00:00
|
|
|
eprintln!(
|
|
|
|
|
"lease_chain: index {index} contract {cid} did not reach running: {e}"
|
|
|
|
|
);
|
|
|
|
|
if let Err(de) =
|
|
|
|
|
destroy_instance_with_retry(client, base_url, api_key, cid).await
|
|
|
|
|
{
|
|
|
|
|
eprintln!(
|
|
|
|
|
"lease_chain: WARNING could not destroy dead contract {cid}: {de}"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-07-07 10:40:02 +00:00
|
|
|
eprintln!("lease_chain: replacing index {index}");
|
2026-06-23 20:10:41 +00:00
|
|
|
match provision_one(
|
|
|
|
|
client,
|
|
|
|
|
base_url,
|
|
|
|
|
api_key,
|
|
|
|
|
&req,
|
|
|
|
|
&pool,
|
|
|
|
|
index,
|
|
|
|
|
&mut tried_offer_ids,
|
|
|
|
|
&mut used_host_ids,
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
&mut failed_host_ids,
|
|
|
|
|
None,
|
2026-06-23 20:10:41 +00:00
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
Ok(info) => created[idx] = info,
|
|
|
|
|
Err(pe) => {
|
|
|
|
|
let survivors: Vec<InstanceInfo> = created
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.filter(|(i, _)| *i != idx)
|
|
|
|
|
.map(|(_, c)| InstanceInfo {
|
|
|
|
|
contract_id: c.contract_id,
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
rollback(client, base_url, api_key, &survivors).await;
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"lease_chain: index {index} replacement could not be provisioned: {pe}"
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(ProvisionedFleet {
|
|
|
|
|
label: req.label,
|
|
|
|
|
instances: created,
|
|
|
|
|
})
|
|
|
|
|
}
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
use wiremock::matchers::{method, path};
|
|
|
|
|
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::types::{LifecyclePolicy, SelectionPolicy};
|
|
|
|
|
|
|
|
|
|
fn offer(id: u64, host_id: u64) -> serde_json::Value {
|
|
|
|
|
json!({
|
|
|
|
|
"id": id,
|
|
|
|
|
"gpu_name": "RTX 4090",
|
|
|
|
|
"dph_total": id as f64 / 100.0,
|
|
|
|
|
"gpu_ram": 24_000.0,
|
|
|
|
|
"compute_cap": 890,
|
|
|
|
|
"geolocation": "US",
|
|
|
|
|
"internet_down_cost_per_tb": 0.0,
|
|
|
|
|
"internet_up_cost_per_tb": 0.0,
|
|
|
|
|
"host_id": host_id,
|
|
|
|
|
"verification": "verified"
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn request(count: u32) -> ProvisionRequest {
|
|
|
|
|
ProvisionRequest {
|
|
|
|
|
count,
|
refactor: `mvp-system` is now a standalone app, `myelin`
Promote the `mvp-system` workspace library crate to a standalone application at `apps/myelin`, rebranding the MVP system along with its binaries, node image, and spec.
- workspace `Cargo.toml`: swap member `crates/mvp-system` -> `apps/myelin` and drop `apps` from `exclude` so the app joins the workspace
- `apps/myelin/Cargo.toml`: declare package `myelin` with `autobins = false` and explicit `[[bin]]` targets `myelin-worker`, `myelin-orchestrator`, `myelin-chat`
- `apps/myelin/src`: move the whole `mvp-system` source tree and rebrand module surfaces (`chat/mod.rs`, `prompt/mod.rs`); add `bin/chat.rs` (`myelin::run_chat_from_args`) and delete the old `mvp_chat.rs`
- `apps/myelin/node-image`: relocate the worker image assets from `apps/mvp-node/` (Dockerfile, Dockerfile.base, tinygrad_worker.py, entrypoint, e2e script) and rename `MVP_SYSTEM_SPEC.md` -> `MYELIN_SPEC.md`
- `xtask`: rewrite build/reference paths for the rename (~1000-line churn); add `crates/dashboard/ACTOR_PANEL_SPEC.md`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-08-01 09:46:55 +00:00
|
|
|
image: "registry.example/myelin-worker:latest".to_owned(),
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
label: Some("lease-test".to_owned()),
|
|
|
|
|
disk_gb: 80,
|
|
|
|
|
env: BTreeMap::new(),
|
|
|
|
|
per_instance_env: Vec::new(),
|
|
|
|
|
preferred_offer_id: None,
|
|
|
|
|
onstart: None,
|
|
|
|
|
selection: SelectionPolicy {
|
|
|
|
|
drop_cheap_frac: 0.0,
|
|
|
|
|
..SelectionPolicy::default()
|
|
|
|
|
},
|
|
|
|
|
lifecycle: LifecyclePolicy {
|
|
|
|
|
lease_pace: Duration::ZERO,
|
|
|
|
|
poll_interval: Duration::from_millis(1),
|
|
|
|
|
state_timeout: Duration::from_millis(5),
|
|
|
|
|
},
|
|
|
|
|
confirm_lease: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn replacement_excludes_failed_host_from_shared_offer_pool() {
|
|
|
|
|
let server = MockServer::start().await;
|
|
|
|
|
Mock::given(method("GET"))
|
|
|
|
|
.and(path("/api/v0/bundles/"))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
|
|
|
|
"offers": [
|
|
|
|
|
offer(1, 10),
|
|
|
|
|
offer(2, 20),
|
|
|
|
|
offer(3, 10),
|
|
|
|
|
offer(4, 30)
|
|
|
|
|
]
|
|
|
|
|
})))
|
|
|
|
|
.mount(&server)
|
|
|
|
|
.await;
|
|
|
|
|
for (offer_id, contract_id) in [(1, 101), (2, 102), (4, 104)] {
|
|
|
|
|
Mock::given(method("PUT"))
|
|
|
|
|
.and(path(format!("/api/v0/asks/{offer_id}/")))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
|
|
|
|
"new_contract": contract_id
|
|
|
|
|
})))
|
|
|
|
|
.mount(&server)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
Mock::given(method("GET"))
|
|
|
|
|
.and(path("/api/v0/instances/101/"))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
|
|
|
|
"instances": {
|
fix: faster provisioning, better ssh checks
Speed up VastAI provisioning by creating instances directly from a cached offer pool, and replace best-effort SSH readiness with classified, post-grace bootstrap-failure detection plus a dedicated per-node provider-status monitor.
- vastai_provisioning: provision_one now iterates a cached candidate_pool of offers calling create_instance directly (with host blacklist and failed-host dedup) instead of re-running client.provision; plan_first_wave_offers caches planned_offer_pool/planned_offer_ids for reuse
- vastai_provisioning: add VastAiProviderMonitor (background thread + AtomicBool stop + Drop) spawned per node via the new spawn_provider_monitor trait method, polling instance_status and emitting VastAiProviderStatusObserved/PollRetry/StatusFailure and terminal-start failures
- vastai_provisioning: add classify_ssh_observation (auth_denied/refused/timeout) with spawn_classifying_stderr_reader; spawn_retrying_ssh_bootstrap aborts after POST_GRACE_BOOTSTRAP_FAILURE_LIMIT repeated classified failures past the grace window instead of retrying forever
- vastai_provisioning: ssh_endpoint delegates to client.wait_for_ssh_endpoint; VastAiNode carries run_id/node_id/label/sink and emits structured VastAiLeaseReady/SshEndpointDiscoveryStarted/SshEndpointReady/RuntimeReadyAccepted/ContractCleanup events; LifecyclePolicy is threaded into start_bootstrap
- tools/vastai: add fetch_instance_status and wait_for_ssh_endpoint_with_policy, refactor wait_for_running onto fetch_instance_status, and export both plus ProviderInstanceStatus from lib.rs
- tools/vastai/types: add ProviderInstanceStatus (actual/intended status, status_msg, public_ipaddr, ssh_port, disk_usage) with ssh_endpoint() and From<InstanceStatus>
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-27 08:30:49 +00:00
|
|
|
"actual_status": "error",
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
"intended_status": "running",
|
fix: faster provisioning, better ssh checks
Speed up VastAI provisioning by creating instances directly from a cached offer pool, and replace best-effort SSH readiness with classified, post-grace bootstrap-failure detection plus a dedicated per-node provider-status monitor.
- vastai_provisioning: provision_one now iterates a cached candidate_pool of offers calling create_instance directly (with host blacklist and failed-host dedup) instead of re-running client.provision; plan_first_wave_offers caches planned_offer_pool/planned_offer_ids for reuse
- vastai_provisioning: add VastAiProviderMonitor (background thread + AtomicBool stop + Drop) spawned per node via the new spawn_provider_monitor trait method, polling instance_status and emitting VastAiProviderStatusObserved/PollRetry/StatusFailure and terminal-start failures
- vastai_provisioning: add classify_ssh_observation (auth_denied/refused/timeout) with spawn_classifying_stderr_reader; spawn_retrying_ssh_bootstrap aborts after POST_GRACE_BOOTSTRAP_FAILURE_LIMIT repeated classified failures past the grace window instead of retrying forever
- vastai_provisioning: ssh_endpoint delegates to client.wait_for_ssh_endpoint; VastAiNode carries run_id/node_id/label/sink and emits structured VastAiLeaseReady/SshEndpointDiscoveryStarted/SshEndpointReady/RuntimeReadyAccepted/ContractCleanup events; LifecyclePolicy is threaded into start_bootstrap
- tools/vastai: add fetch_instance_status and wait_for_ssh_endpoint_with_policy, refactor wait_for_running onto fetch_instance_status, and export both plus ProviderInstanceStatus from lib.rs
- tools/vastai/types: add ProviderInstanceStatus (actual/intended status, status_msg, public_ipaddr, ssh_port, disk_usage) with ssh_endpoint() and From<InstanceStatus>
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-27 08:30:49 +00:00
|
|
|
"status_msg": "container failed before runtime readiness"
|
feat: per-stage GGUF weight sharding and deploy hardening
Distribute only each stage's GGUF layer slice over HTTP, add sampler and weight-load health telemetry, and harden node-image build, orchestrator provisioning, and the VastAI lease/search path.
- gguf_shard (new): StageShardPlan and plan_stage_shard parse the GGUF directory and compute coalesced per-stage tensor byte ranges; materialize_stage_shard_http fetches only those ranges (plus the header) to build a stage-local GGUF, with planned_fetch_bytes accounting.
- orchestrator_app: build a BTreeMap<u32, StageShardPlan> from the run plan for HuggingFace sources, thread stage_shard_plan through StageProvisionWire and weight-load, emit stage_shard_plan summaries, and add liveness phases (prefetching/fetching_stage_shard, cache_ready, stage_shard_ready).
- worker_node: add a stage-shard-fetcher subcommand and materialize_stage_shard_with_process that spawns the fetcher, streams its stdout/stderr as stage_shard_fetch events (StageShardCacheReady/StageShardReady), caches under MVP_MODEL_CACHE_DIR, and feeds the local shard path into load_weights.
- worker_node: add NODE_SAMPLER_CHANNEL and SamplerHealth telemetry (gpu/cpu/net samplers emit started/waiting/ready/failed) plus structured helper stdout/stderr streaming (wait_for_helper_event/drain_worker_stderr).
- node_image: expand node-image build/push handling for the deploy path.
- tools/vastai: extend lease, search, and types and drop unused pricing code.
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-26 09:01:18 +00:00
|
|
|
}
|
|
|
|
|
})))
|
|
|
|
|
.mount(&server)
|
|
|
|
|
.await;
|
|
|
|
|
for contract_id in [102, 104] {
|
|
|
|
|
Mock::given(method("GET"))
|
|
|
|
|
.and(path(format!("/api/v0/instances/{contract_id}/")))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
|
|
|
|
"instances": {
|
|
|
|
|
"actual_status": "running",
|
|
|
|
|
"intended_status": "running",
|
|
|
|
|
"public_ipaddr": "127.0.0.1",
|
|
|
|
|
"ssh_port": 22
|
|
|
|
|
}
|
|
|
|
|
})))
|
|
|
|
|
.mount(&server)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
Mock::given(method("DELETE"))
|
|
|
|
|
.and(path("/api/v0/instances/101/"))
|
|
|
|
|
.respond_with(ResponseTemplate::new(200))
|
|
|
|
|
.mount(&server)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let fleet = provision_fleet(&reqwest::Client::new(), &server.uri(), "secret", request(2))
|
|
|
|
|
.await
|
|
|
|
|
.expect("replacement should use non-failed host");
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
fleet
|
|
|
|
|
.instances
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|instance| (instance.index, instance.offer_id, instance.host_id))
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
vec![(0, 4, Some(30)), (1, 2, Some(20))]
|
|
|
|
|
);
|
|
|
|
|
let requests = server.received_requests().await.expect("recorded requests");
|
|
|
|
|
assert!(
|
|
|
|
|
requests
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|request| request.url.path() == "/api/v0/asks/4/"),
|
|
|
|
|
"replacement should rent an offer from a non-failed host"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
!requests
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|request| request.url.path() == "/api/v0/asks/3/"),
|
|
|
|
|
"replacement must skip untried offers on the failed host"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|