This commit is contained in:
@@ -5,10 +5,14 @@
|
||||
* a rename rather than a transformation. The interesting parts are the two
|
||||
* places where a judgement is required.
|
||||
*
|
||||
* **Money.** Upstream prices are floating-point dollars per hour. PIG stores
|
||||
* integer cents, because these values feed margin reporting and floating-point
|
||||
* currency in a system that reports margin is a defect waiting to be found by
|
||||
* an accountant. Rounding happens exactly once, here, at the boundary.
|
||||
* **Node totals.** Upstream reports `gpuMemory` and `prices.onDemand` for the
|
||||
* whole node, while PIG compares inventory per GPU. Normalising here prevents
|
||||
* larger nodes from appearing to have more expensive GPUs, and `raw` keeps the
|
||||
* original totals for reconciliation.
|
||||
*
|
||||
* **Money.** PIG stores integer cents, because floating-point currency in a
|
||||
* system that reports margin is a defect waiting to be found by an accountant.
|
||||
* Rounding happens exactly once, here, after normalising to a per-GPU price.
|
||||
*
|
||||
* **Interconnect.** The single most commercially loaded field, since it decides
|
||||
* whether capacity can train or only serve. Upstream sends free text with
|
||||
@@ -58,7 +62,7 @@ export function mapListing(listing: PrimeGpuListing): MappedListing | null {
|
||||
gpuType: listing.gpuType,
|
||||
socket: normaliseSocket(listing.socket),
|
||||
gpuCount: listing.gpuCount,
|
||||
gpuMemoryGb: listing.gpuMemory ?? null,
|
||||
gpuMemoryGb: perGpu(listing.gpuMemory, listing.gpuCount),
|
||||
vcpu: unwrapCount(listing.vcpu),
|
||||
memoryGb: unwrapCount(listing.memory),
|
||||
diskGb: listing.disk?.defaultCount ?? null,
|
||||
@@ -72,7 +76,7 @@ export function mapListing(listing: PrimeGpuListing): MappedListing | null {
|
||||
isSpot: listing.isSpot ?? false,
|
||||
provisioningMinutes: listing.provisioningTime ?? null,
|
||||
prepaidHours: listing.prepaidTime != null ? String(listing.prepaidTime) : null,
|
||||
onDemandPriceCents: toCents(listing.prices?.onDemand),
|
||||
onDemandPriceCents: toCents(perGpu(listing.prices?.onDemand, listing.gpuCount)),
|
||||
communityPriceCents: toCents(listing.prices?.communityPrice),
|
||||
priceIsVariable: listing.prices?.isVariable ?? false,
|
||||
currency: listing.prices?.currency ?? 'USD',
|
||||
@@ -94,6 +98,11 @@ export function toCents(dollars: number | null | undefined): number | null {
|
||||
return Math.round(dollars * 100);
|
||||
}
|
||||
|
||||
function perGpu(total: number | null | undefined, gpuCount: number): number | null {
|
||||
if (total == null || !Number.isFinite(total)) return null;
|
||||
return total / gpuCount;
|
||||
}
|
||||
|
||||
function unwrapCount(value: unknown): number | null {
|
||||
if (typeof value === 'number') return value;
|
||||
if (value && typeof value === 'object') {
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
* be represented exactly in binary, and a cent compounds across millions of
|
||||
* GPU-hours.
|
||||
*
|
||||
* **Node totals.** Prime reports on-demand price and GPU memory for the whole
|
||||
* node. PIG compares per-GPU values, so a larger node must not look more
|
||||
* expensive merely because it contains more GPUs.
|
||||
*
|
||||
* **Interconnect.** The field that decides whether capacity can train or
|
||||
* only serve. Guessing wrong in either direction loses a deal or sells a
|
||||
* customer a cluster that cannot do the job.
|
||||
@@ -99,11 +103,39 @@ describe('mapListing — general', () => {
|
||||
});
|
||||
|
||||
it('converts prices to integer cents', () => {
|
||||
const m = mapListing(listing({ prices: { onDemand: 2.43, communityPrice: 0.94 } }))!;
|
||||
const m = mapListing(
|
||||
listing({ gpuCount: 1, prices: { onDemand: 2.43, communityPrice: 0.94 } }),
|
||||
)!;
|
||||
assert.equal(m.onDemandPriceCents, 243);
|
||||
assert.equal(m.communityPriceCents, 94);
|
||||
});
|
||||
|
||||
it('normalises verified DataCrunch A100 node totals to the same per-GPU values', () => {
|
||||
const oneGpuRaw = {
|
||||
provider: 'datacrunch',
|
||||
gpuType: 'A100_80GB',
|
||||
gpuCount: 1,
|
||||
gpuMemory: 80,
|
||||
prices: { onDemand: 1.79 },
|
||||
};
|
||||
const twoGpuRaw = {
|
||||
provider: 'datacrunch',
|
||||
gpuType: 'A100_80GB',
|
||||
gpuCount: 2,
|
||||
gpuMemory: 160,
|
||||
prices: { onDemand: 3.58 },
|
||||
};
|
||||
|
||||
const oneGpu = mapListing({ ...oneGpuRaw, raw: oneGpuRaw })!;
|
||||
const twoGpu = mapListing({ ...twoGpuRaw, raw: twoGpuRaw })!;
|
||||
|
||||
assert.equal(oneGpu.onDemandPriceCents, 179);
|
||||
assert.equal(twoGpu.onDemandPriceCents, 179);
|
||||
assert.equal(oneGpu.gpuMemoryGb, 80);
|
||||
assert.equal(twoGpu.gpuMemoryGb, 80);
|
||||
assert.deepEqual(twoGpu.raw, twoGpuRaw);
|
||||
});
|
||||
|
||||
it('defaults to the secure tier unless community is stated', () => {
|
||||
// Mislabelling community capacity as secure would let it be sold against a
|
||||
// requirement it cannot meet.
|
||||
|
||||
Reference in New Issue
Block a user