Repository navigation
Conversation
Lium (https://lium.io) rents NVIDIA GPU pods and publishes its whole rentable inventory as one unauthenticated JSON feed with per-GPU hourly prices, so the provider needs no credentials and is queried live as an online provider. - Feed: https://lium.io/api/public/v1/nodes (contract documented at https://docs.lium.io/developers/public-nodes-feed; fields under /public/v1 are only ever added). - A node can be rented in part: one offer is produced per GPU count between `min_rentable_gpu_count` and `available_gpu_count`, with CPUs, RAM and disk in proportion to the GPU share, as Lium allocates them (the same expansion the jarvislabs provider does). - `price` is the per-GPU-hour price times the offer's GPU count. - `gpu_name` is normalized to the KNOWN_NVIDIA_GPUS names (`RTX 5090` -> `RTX5090`, `H100 80GB HBM3` -> `H100`, `RTX PRO 6000 Blackwell Server Edition` -> `RTXPRO6000`); every model in the live feed resolves to a known GPU, and a model that does not is skipped with a warning, like the seeweb and jarvislabs providers do. `gpu_memory` goes through `correct_gpu_memory_gib`. - `location` is `<iso country>-<subdivision>` (`us-ca`, `jp-13`). - `spot` is True for nodes in Lium's spot tier (provider may reclaim). - `instance_name` is the node id, which is what the rent API takes. - Unit tests run against a recorded feed sample; the integrity test queries the live feed like the other online providers.
|
Gentle ping: this PR is still green and merges cleanly. It adds Lium as a provider using only the public, unauthenticated node feed (https://lium.io/api/public/v1/nodes, contract at https://docs.lium.io/developers/public-nodes-feed.md), so the catalog refresh needs no credentials. Happy to adjust anything to your conventions or to be listed as the maintainer for this provider. |
…er to the whole node
peterschmidt85
left a comment
There was a problem hiding this comment.
The PR looks good to me now. Thanks for addressing both comments. Just one final ask: could we also handle B300 SXM6 PC?
Vast lists B300 boards that report "NVIDIA B300 SXM6 PC" as "B300 PC", next to the "B300" it uses for "NVIDIA B300 SXM6 AC" boards. get_dstack_gpu_name turned that into "B300PC", which is not a known GPU, so those offers never matched a request for B300. Normalize "B300 PC" to "B300", and ask Vast for both names when the query is for B300. This is the same normalization requested for Lium in #259 (comment) Co-authored-by: Harry Eakins <harry.eakins@span.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
peterschmidt85
left a comment
There was a problem hiding this comment.
Thanks @surcyf123, the earlier fixes look good. One remaining ask on disk allocation below. I prepared and tested a fix locally, but GitHub won't let me push to your fork.
Could you also add $50 in credits to andrey.cheptsov@gmail.com so we can test reliability and explore adding a dstack backend?
Use disk_free_gb, not total disk_gb, and sum the separately rounded-down 2/3 volume and 1/3 container storage quotas (docs.lium.io/pod-users/create-pod). Skip nodes with no free disk reported and offers whose quotas round to zero, since disk_size=None passes every disk filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@peterschmidt85 thanks. The disk fix is in 0fc9b50 (details in the thread). I've passed the $50 test-credit request for andrey.cheptsov@gmail.com to the team, and I'll post here once it's decided. If that address has no Lium account yet, signing up at https://lium.io with it lets the credit go straight on. |
|
@peterschmidt85 $50 in credits is now on the Lium account for andrey.cheptsov@gmail.com. It's valid for 90 days, until 13 Jan 2027, and a confirmation email went to that address. Thanks for looking at a dstack backend; if anything comes up while you're testing, just ask here. |
peterschmidt85
left a comment
There was a problem hiding this comment.
Thanks for the credits, @surcyf123. We now have a draft Lium backend in dstackai/dstack#4377. Focused E2E checks passed for private images, non-root jobs, root-volume persistence, cancellation, and cleanup.
Could you try the backend from that PR with your own workloads and share feedback on the integration? We'd like your input before merging it.
I left two resource-accounting issues found during testing below. Once we resolve those and get your backend feedback, we can proceed with merging/releasing gpuhunt and merging the backend.
| # proportion to its GPU share. | ||
| share = count / gpu_count | ||
| cpu = int(cpu_count * share) | ||
| memory = max(1.0, math.floor(pod_ram_gb * share * 100) / 100) |
There was a problem hiding this comment.
A live one-GPU H200 rental exposed a remaining RAM mismatch:
share = (2015 - max(4, 2015 * 0.01)) / 8
math.floor(share * 100) / 100 # 249.35 GiB advertised
267361714176 / 2**30 # 249 GiB, measured memory.maxSo QueryFilter(min_memory=249.1) accepts an offer whose rental only has 249 GiB.
Could we use unrounded host RAM or an authoritative per-rental allocation, and add boundary filtering tests? Simply flooring the current result isn't sufficient: another host's feed says 63 GiB, while its raw telemetry says 62.796 GiB. After the 4 GiB reserve, those round down to 59 versus 58 GiB. That second example is from telemetry, not a rented-pod measurement.
There was a problem hiding this comment.
Done in a405412. Lium sizes memory.max as the pod's RAM share rounded down to whole GiB. The feed rounds ram_gb to the nearest GiB, so the provider now starts from the smallest host value consistent with the feed (ram_gb - 0.5), takes off the host reserve and rounds the share down to whole GiB. The one-GPU H200 now reports 249, which matches the measured memory.max. It can come out up to 1 GiB below the real allocation, but it never comes out above it. test_memory_filter_boundary pins it: min_memory=249 matches and min_memory=249.1 does not.
If you'd rather have it exact, we can add an unrounded per-GPU allocation field to the public feed (fields are only ever added under /public/v1) and switch to it later.
| >>> get_disk_quota_gb(1) | ||
| 0 | ||
| """ | ||
| return int(disk_gb * 2 / 3) + int(disk_gb / 3) |
There was a problem hiding this comment.
You implemented exactly what I asked for; I need to correct that advice after integration testing—sorry. These are separate filesystem quotas, so adding them doesn't give a job that much space in one directory.
Using the same 1467 GB / 8 GPU example:
root_quota = int((1467 / 8) * 2 / 3) # 122
container_quota = int((1467 / 8) / 3) # 61
# Current disk_size: 122 + 61 = 183A 150 GB disk request matches, although neither filesystem can hold that much. The backend's /workflow directory uses the container filesystem, which gets 61 GB in this example.
Could we agree on and report the capacity available at the job's storage path, rather than the sum, and update the disk-filtering tests? With the current /workflow layout, that would be 61 GB here.
There was a problem hiding this comment.
Done in a405412. disk_size is now only the container storage quota, the filesystem that holds /workflow: int((disk_free_gb - 0.5) * share / 3). The - 0.5 makes it a lower bound, because the feed rounds free disk to the nearest GB. Your example now gives 61 GB. test_disk_filter_uses_the_pod_quota checks that 150 GB and 62 GB requests don't match it while 61 GB does, and offers whose quota rounds to zero are skipped.
…unds The feed rounds RAM and free disk to the nearest GB, so size offers from the lowest true value and round down to whole GiB, matching the pod's memory.max. Disk is now the container storage quota (1/3 of the share) that holds /workflow, not volume + storage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@peterschmidt85 both resource issues are fixed in a405412 (details in the two threads): memory is whole-GiB |
|
@peterschmidt85 our feedback on the backend is in dstackai/dstack#4377 (comment). In short, the task, dev environment, cancel during provisioning and cleanup all worked against this branch at a405412. On live pods, memory, CPUs and disk matched or exceeded what the offers advertised. Nothing on the gpuhunt side is left open from our end. |
|
@surcyf123 one issue from testing the dstack backend: S3-backed volumes don't work for non-root jobs. On a fresh unencrypted A6000 pod, UID 1000 can create a We also checked bindfs, but ordinary unencrypted pods don't expose |
|
@peterschmidt85 thanks for the precise report. It's a bug on our side, and we've found the cause. An S3 volume is mounted with s3fs, and the mount reports every object as owned by the pod's root. s3fs checks permissions itself against that owner and the stored mode. So a UID 1000 job creates a The fix is to mount S3 volumes with The fix is built and in review, not yet released. It applies to pods rented after the release, and I'll post here when it is live so you can retest with dstack. |
|
@surcyf123 thanks for tracking this down and fixing it! Please let us know once it's live so we can retest non-root volume access on fresh pods. |
Summary
Adds Lium as an online catalog provider.
LiumProvider.from_env()iscls(), likevastai. Feed: https://lium.io/api/public/v1/nodes — contract at https://docs.lium.io/developers/public-nodes-feed (fields under/public/v1are only ever added; breaking changes ship as/public/v2). Rate limit 60 req/min per IP, snapshot rebuilt once a minute.min_rentable_gpu_countandavailable_gpu_count(the same expansionjarvislabsdoes), with CPUs, RAM and disk in proportion to the GPU share, which is how Lium allocates them (field notes).priceis the per-GPU-hour price times the offer's GPU count.gpu_nameis normalized to theKNOWN_NVIDIA_GPUSnames (RTX 5090→RTX5090,H100 80GB HBM3→H100,A100-SXM4-80GB→A100,RTX PRO 6000 Blackwell Server Edition→RTXPRO6000,RTX 6000 Ada Generation→RTX6000Ada). Every model in today's live feed resolves to a known GPU; a model that does not is skipped with a warning, asseewebandjarvislabsdo, and the integrity tests warn about such models in the live feed (liketest_real_world_vastai_offers).gpu_memorygoes throughcorrect_gpu_memory_gib(the feed says 140 GB for the H200, the table says 141).locationis<iso country>-<subdivision>(us-ca,jp-13),spotisTruefor nodes in Lium's spot tier (the provider may reclaim them),instance_nameis the node id — the identifier the rent API takes (POST /executors/{id}/rent, https://lium.io/api/openapi.json), so a future dstack backend can rent straight from the offer.provider_datacarries therent_urldeep link.ONLINE_PROVIDERS,ONLINE_PROVIDER_MODULES, the CLI (python -m gpuhunt lium), README, and the catalogs workflow (test-liumintegrity job, no secrets needed).Testing
uv run pytest src/tests— 168 passed (45 new provider tests, 16 of them against a feed sample recorded on 2026-09-10; no network)uv run pytest --doctest-modules src/gpuhunt— 4 passeduv run pytest src/integrity_tests/test_lium.py— 11 passed against the live feeduv run pyright— 0 errors;pre-commit run -a(ruff, ruff-format) — passedpython -m gpuhunt lium --output lium.csv— 117 offers from 84 nodes at the time of writingNotes
I work on Lium. Happy to adjust anything to the conventions you prefer — for example collapsing the per-GPU-count expansion to whole-node offers only.