Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ members = [
]

[workspace.package]
version = "0.2.34"
version = "0.2.35"
edition = "2024"
license = "MIT"
repository = "https://github.com/profullstack/c0mpute"
Expand Down
2 changes: 1 addition & 1 deletion apps/tui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@c0mpute/tui",
"version": "0.2.34",
"version": "0.2.35",
"private": true,
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "c0mpute",
"version": "0.2.34",
"version": "0.2.35",
"private": true,
"type": "module",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
## Run a node

- [Run a node](https://c0mpute.com/providers): `curl -fsSL https://c0mpute.com/install.sh | sh` installs c0mpute and starts the worker (Linux, macOS, Windows via WSL2; CPU or GPU). The goal is 100,000 nodes, enough pooled capacity to train and serve open-weight models in the open.
- [Market](https://c0mpute.com/market): GPU nodes online with each operator's listing. `c0mpute pricing set --min <usd>` = dynamic pricing (idle bids 60% of the buyer's cap, rising to the cap as the queue fills) never under the floor; `--fixed <usd>` = one price. The listing (`pricing: {mode, min_usd, fixed_usd}`) is in https://c0mpute.com/api/nodes.
- [Node board](https://c0mpute.com/nodes): every node online now, ranked by `c0mpute bench` score. JSON at https://c0mpute.com/api/nodes.
- Referral links: https://c0mpute.com/r/<handle> gives an install line with `C0MPUTE_REF=<handle>`; nodes installed with it show the handle on the board. Public scoreboard, no payout.

Expand Down
23 changes: 21 additions & 2 deletions apps/web/src/app/market/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from "next/link";
import { CodeBlock } from "@/components/CodeBlock";
import { getNodesPayload } from "@/lib/nodes";
import { MARKET_FEE_PCT, OPERATOR_SHARE_PCT, splitPrice } from "@/lib/market";
import { getNodesPayload, priceLabel } from "@/lib/nodes";
import { DYNAMIC_IDLE_SHARE, MARKET_FEE_PCT, OPERATOR_SHARE_PCT, splitPrice } from "@/lib/market";

export const metadata = {
title: "market — c0mpute",
Expand Down Expand Up @@ -91,6 +91,7 @@ export default async function MarketPage() {
<th className="py-2 pr-3">node</th>
<th className="py-2 pr-3">class</th>
<th className="py-2 pr-3">bench</th>
<th className="py-2 pr-3">price / job</th>
<th className="py-2 pr-3">version</th>
<th className="py-2">seen</th>
</tr>
Expand All @@ -101,6 +102,7 @@ export default async function MarketPage() {
<td className="py-2 pr-3 accent font-mono">{n.id}</td>
<td className="py-2 pr-3">{n.class}</td>
<td className="py-2 pr-3">{n.bench_score?.toLocaleString() ?? "-"}</td>
<td className="py-2 pr-3">{priceLabel(n.pricing)}</td>
<td className="py-2 pr-3 text-[var(--color-dim)]">{n.version ?? "-"}</td>
<td className="py-2 text-[var(--color-dim)]">{ago(n.last_seen_s)}</td>
</tr>
Expand Down Expand Up @@ -141,6 +143,23 @@ $ c0mpute transcode submit input.mov --preset hls --max-price 1.25`}</CodeBlock>
</p>
<CodeBlock>{`$ curl -fsSL https://c0mpute.com/install.sh | sh
$ infernet payout set USDC 0xYourAddress --network polygon`}</CodeBlock>
<p>
<strong className="text-[var(--color-fg)]">Set your price.</strong>{" "}
Every node is listed with dynamic pricing by default: when it is idle
it bids {Math.round(DYNAMIC_IDLE_SHARE * 100)}% of the buyer&apos;s
cap to win work, and the bid rises toward the full cap as its job
queue fills. You set a minimum and it never bids under it; jobs capped
below your minimum are skipped. Or ask one fixed price.
</p>
<CodeBlock>{`$ c0mpute pricing set --min 0.05 # dynamic, never under $0.05 a job
$ c0mpute pricing set --fixed 0.40 # always $0.40 a job
$ c0mpute pricing # show your listing
$ c0mpute worker stop && c0mpute worker start -d # apply`}</CodeBlock>
<p className="text-xs text-[var(--color-dim)] leading-6">
Your listing (mode and minimum) rides in the node&apos;s capability
ad and shows in the table above and in <a href="/api/nodes">/api/nodes</a>.
The {MARKET_FEE_PCT}% market fee comes out of the price you win the job at.
</p>
<p className="text-xs text-[var(--color-dim)]">
→ <Link href="/providers">running a node</Link>
</p>
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/app/providers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ $ c0mpute worker status # the installer already started it`}</CodeBlock>
<section className="space-y-3 text-sm leading-7">
<h2 className="font-semibold text-[var(--color-fg)]">[ what you earn ]</h2>
<p>
You set your rates. A buyer sets a cap. When you quote under it and
the work validates, settlement releases and both sides sign a
You set your rates: dynamic by default (undercut when idle, up to the
buyer&apos;s cap when busy) or one fixed price, never under the
minimum you set with <code>c0mpute pricing set --min 0.05</code>. A
buyer sets a cap. When you quote under it and the work validates, settlement releases and both sides sign a
receipt. A direct protocol deal has{" "}
<strong className="text-[var(--color-fg)]">no protocol fee</strong>. Jobs
bought through the <Link href="/market">market</Link>, including paid
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/lib/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ export function splitPrice(usd: number): { operator: number; fee: number } {
const fee = Math.round((cents * MARKET_FEE_PCT) / 100);
return { operator: (cents - fee) / 100, fee: fee / 100 };
}

/**
* Share of the buyer's cap an idle node bids in dynamic mode; the bid rises to
* the full cap as its queue fills, never under the operator's minimum. Mirrors
* `DYNAMIC_IDLE_SHARE` in node/crates/c0mpute-core/src/pricing.rs.
*/
export const DYNAMIC_IDLE_SHARE = 0.6;
19 changes: 19 additions & 0 deletions apps/web/src/lib/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,28 @@ export interface NodeRow {
bench_score: number | null;
version: string | null;
ref: string | null;
/** The operator's listing; absent on nodes older than 0.2.35. */
pricing?: Pricing | null;
last_seen_s: number;
}

/** How a node prices its bids, set by its operator with `c0mpute pricing set`. */
export interface Pricing {
mode: "dynamic" | "fixed";
min_usd: number;
fixed_usd?: number;
}

/** "dynamic · min $0.02" / "fixed $0.30" / "-" for the market table. */
export function priceLabel(p: Pricing | null | undefined): string {
if (!p) return "-";
const usd = (n: number) => `$${n < 0.01 && n > 0 ? n : n.toFixed(2)}`;
if (p.mode === "fixed" && p.fixed_usd != null) {
return `fixed ${usd(Math.max(p.fixed_usd, p.min_usd))}`;
}
return p.min_usd > 0 ? `dynamic · min ${usd(p.min_usd)}` : "dynamic";
}

export interface NodesPayload {
generated_at: string;
workers_online: number;
Expand Down
72 changes: 71 additions & 1 deletion node/crates/c0mpute-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ enum Cmd {
#[arg(long, value_delimiter = ',', global = true)]
threads: Option<Vec<usize>>,
},
/// Your listing on c0mpute.com/market: how this node prices its bids.
///
/// Dynamic (default) undercuts the buyer's cap when idle and rises as the
/// queue fills; fixed always asks one price. Neither goes under `--min`.
/// A running worker picks a change up on restart.
Pricing {
#[command(subcommand)]
cmd: Option<PricingCmd>,
},
/// Worker lifecycle.
Worker {
#[command(subcommand)]
Expand Down Expand Up @@ -484,6 +493,7 @@ async fn run_app(cli: Cli) -> Result<()> {
threads,
} => run_bench(cmd, quick, json, threads),
Cmd::StatusAggregator { bind } => c0mpute_core::status_aggregator::run(bind).await,
Cmd::Pricing { cmd } => run_pricing(cmd, &config_path),
Cmd::Worker { cmd } => run_worker(cmd, &config_path).await,
Cmd::Job { cmd } => run_job(cmd).await,
Cmd::Storage { cmd } => storage::run(cmd, &config_path).await,
Expand All @@ -496,7 +506,9 @@ async fn run_app(cli: Cli) -> Result<()> {
Cmd::Uninstall { all, purge, yes } => run_uninstall(all, purge, yes),
Cmd::Coinpay { args } => delegate("coinpay", &args),
Cmd::Login => run_login(),
Cmd::Infernet { args } if args.first().map(String::as_str) == Some("temporal") => temporal::run(&args[1..]).await,
Cmd::Infernet { args } if args.first().map(String::as_str) == Some("temporal") => {
temporal::run(&args[1..]).await
}
Cmd::Infernet { mut args } => {
// Default the network to c0mpute when caller didn't specify.
if matches!(args.first().map(String::as_str), Some("run"))
Expand Down Expand Up @@ -723,6 +735,64 @@ async fn run_worker(cmd: WorkerCmd, config_path: &std::path::Path) -> Result<()>
// bench
// ────────────────────────────────────────────────────────────────────────

#[derive(Subcommand, Debug)]
enum PricingCmd {
/// Print the current listing (the default).
Show,
/// Set the floor and the mode.
Set {
/// Lowest price per job you accept, in USD.
#[arg(long)]
min: Option<f64>,
/// Always ask this price per job (fixed mode).
#[arg(long, conflicts_with = "dynamic")]
fixed: Option<f64>,
/// Price by demand: undercut when idle, up to the buyer's cap when busy.
#[arg(long)]
dynamic: bool,
},
}

fn run_pricing(cmd: Option<PricingCmd>, config_path: &std::path::Path) -> Result<()> {
use c0mpute_core::pricing::PricingMode;
let mut cfg = Config::load_or_default(config_path)?;
if let Some(PricingCmd::Set {
min,
fixed,
dynamic,
}) = cmd
{
if let Some(m) = min {
cfg.pricing.min_usd = m;
}
if let Some(f) = fixed {
cfg.pricing.mode = PricingMode::Fixed;
cfg.pricing.fixed_usd = Some(f);
} else if dynamic {
cfg.pricing.mode = PricingMode::Dynamic;
cfg.pricing.fixed_usd = None;
}
cfg.pricing.validate().map_err(anyhow::Error::msg)?;
cfg.save(config_path)?;
println!(
"saved to {} (apply: c0mpute worker stop && c0mpute worker start -d)",
config_path.display()
);
}
let p = &cfg.pricing;
println!("mode : {}", p.mode.as_str());
println!("min : ${} per job", p.min_usd);
if let (PricingMode::Fixed, Some(f)) = (p.mode, p.fixed_usd) {
println!("price : ${f} per job");
}
if let Some(q) = p.quote(1.0, 0.0) {
println!("example : on a $1.00 job you bid ${q:.2} when idle");
} else {
println!("example : you skip jobs capped at $1.00 (under your floor)");
}
Ok(())
}

#[derive(Subcommand, Debug)]
enum BenchCmd {
/// Run the benchmark and save the report (the default).
Expand Down
Loading
Loading