[All writing](/work?type=writing)

# Sharing my GPU with friends

I built GPUShare around my desktop GPU. The accounts, routing and cost estimates ended up needing as much explanation as the hardware.

21 March 2026 · 3 min read

[Self-hosted](/tags/self-hosted) [AI](/tags/ai) [Economics](/tags/economics) [Architecture](/tags/architecture)

![A dark illustration of two wireframe globes, each with a lightning bolt at its centre, beside a panel of text lines.](https://cdn.sanity.io/images/qnuj1c4o/production/771c9d9b7d8ce28db9f38621aa4fa21c9dcf2cc7-680x410.png?w=680\&q=80\&auto=format)

My desktop GPU sits idle most of the day, and I wanted a few people I trust to be able to use it for local AI and Blender rendering. That became GPUShare.

I built the interface in React, with accounts and invites so access stays with a trusted group. There are API keys and an OpenAI-compatible chat API, and I added optional cloud routing, MCP connections and usage accounting around the local AI and rendering.

## The current request path

```
Browser or API client
        ↓
Vercel-hosted React frontend
        ↓
Cloudflare Tunnel → public FastAPI middleware
        ↓  X-Internal-Token
private hardware backend
        ├─ Ollama on the host
        ├─ Blender render worker
        ├─ optional Cloudflare R2 file storage
        └─ optional OpenRouter cloud models
```

The middleware owns JWT and API-key authentication, account and admin operations, billing integration, aggregation, and short-lived in-process caches. Hardware requests are forwarded to a second FastAPI service using a shared secret. The Cloudflare ingress points at the middleware, not the backend. Docker Compose does still publish the backend port on the host, so the application-level internal token remains an important boundary.

## Local AI, with an explicit cloud option

The standard `/v1/chat/completions` and model endpoints follow OpenAI-compatible shapes. Responses can stream over Server-Sent Events, and structured tool calls are normalised for both Ollama and OpenRouter. Local Ollama requests are serialised through a process-local GPU queue.

OpenRouter is optional. A selected OpenRouter model sends the request to that provider, as does the configured `auto` model when its rules pick cloud. It is not an automatic failover that retries a failed Ollama request. When local-only routing is selected, model inference runs on the host; when a cloud route is selected, prompt data leaves the host under that provider’s terms.

## Where the cost estimate comes from

GPUShare does not directly meter the wall-power consumed by each inference request. It estimates local usage with configured or detected GPU and system wattage, the configured electricity tariff, estimated token counts, and a default throughput assumption. Render estimates use elapsed job time and configured wattage. Optional OpenRouter requests use provider pricing.

```
estimated local cost = estimated kWh × configured electricity tariff
```

The calculator below is illustrative and uses the same kind of assumptions; it is not a utility-meter reading.

### GPUShare cost calculator

See what local inference actually costs on a 5070 Ti against cloud APIs.

Model Llama 3.1 8B (BF16) - \~8 GB, \~55 tok/s - Everyday tasks (0)

Tokens per response: 500 500

504,000

Responses per day: 50 50

1500

Electricity rate: NZ$0.346/kWh

0.346

NZ$0.01NZ$0.60

0.346

Per response

NZ$0.0001

Daily

NZ$0.0066

Monthly

NZ$0.20

vs cloud

75.5% lower

Cloud equivalent (GPT-4o mini): **NZ$0.80/mo**

Local (Llama 3.1 8B): **NZ$0.20/mo**

Local cost = 150W GPU draw x inference time x electricity rate. Cloud comparison uses GPT-4o mini output token pricing. Actual throughput varies with quantisation, context length, and batch size.

1 US$ = NZ$1.7819 · [ECB reference rates](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.ff.html) · 2 October 2026

The accounting record itself is append-only: usage charges, cloud usage, render usage, top-ups, payments, and adjustments are separate ledger entry types, and the displayed balance is their sum.

## Blender rendering is a trusted-user feature

Users can submit `.blend` files with frame, resolution, engine, sample, and output settings. The backend validates the file type and size, removes Blender text blocks, uploads the cleaned file to optional R2 storage, and queues a separate worker. Completed output is packaged and exposed through a time-limited signed URL.

Removing embedded text blocks is a useful guardrail, but it is not a full process, filesystem, network, or resource sandbox. The current upload path is suitable only for trusted invited users, not arbitrary public files.

## Operational limits

- If the host or Cloudflare Tunnel is offline, local inference and render submission are offline.
- Cloud routing is optional model selection, not automatic availability failover.
- Local cost and energy values are estimates, not direct per-request measurements.
- The cross-platform setup scripts automate detection and configuration, but their current migration command still names the removed `fastapi` Compose service after the backend/middleware split.
- The repository currently has no first-party automated test suite, so the project should not be presented as production-hardened managed infrastructure.

I’ve ended up with an interface and an accounting system around the GPU. Though it still comes back to a desktop in my house, which sometimes just switches off.

The source is available at [github.com/Slaymish/GPUShare](https://github.com/Slaymish/GPUShare).

## Related

- [Article · 2026 Splitting GPUShare into two services](/posts/splitting-the-stack-and-making-setup-actually-work)
- [Report · 2026 Anomaly (Out-of-Distribution) Detection for Electrical Distribution Networks using Discrete Diffusion Methods](/reports/grid-diffusion-anomaly-detection)
- [Article · 2026 Putting my Apple Health data in one place](/posts/healthagent-apple-health-data-ingestion-and-insights)
