Summary
pricing/ec2.go has no entries for any GPU family released since ~2024, and
estimatePriceByFamily covers the gap by silently inventing a price and
returning err == nil. For modern GPU instances the invented number is 4–12×
below the real rate, and for at least one type it fabricates a price where AWS
publishes none at all.
Found while building cultivar (a HuggingFace→AWS inference deployer whose entire
output is a cost recommendation). Verified live against the AWS Price List API in
account 942542972736, us-east-1, 2026-07-27.
Evidence
GetEC2HourlyRate("us-east-1", …) vs live Price List on-demand
(operatingSystem=Linux, tenancy=Shared, preInstalledSw=NA,
capacitystatus=Used):
| instance type |
GetEC2HourlyRate |
real on-demand |
error |
| g7e.4xlarge |
$0.8000 |
$3.9982 |
5.0× low |
| p5.4xlarge |
$0.8000 |
$6.88 |
8.6× low |
| g6e.12xlarge |
$2.4000 |
$10.49 |
4.4× low |
| p6-b200.48xlarge |
$9.6000 |
$113.93 |
11.9× low |
| p5e.48xlarge |
$9.6000 |
no on-demand price exists |
fabricated |
Reproducer:
import libpricing "github.com/spore-host/libs/pricing"
for _, t := range []string{"g7e.4xlarge", "p5.4xlarge", "p5e.48xlarge",
"p6-b200.48xlarge", "g6e.12xlarge"} {
fmt.Printf("%-20s $%.4f/hr\n", t, libpricing.GetEC2HourlyRate("us-east-1", t))
}
// g7e.4xlarge $0.8000
// p5.4xlarge $0.8000
// p5e.48xlarge $9.6000
// p6-b200.48xlarge $9.6000
// g6e.12xlarge $2.4000
Root cause
basePriceLarge in pricing/ec2.go has no g7, g7e, g6e, p5, p5e,
p6-b200, or p6-b300 key. estimatePriceByFamily (line 183) then does:
base, ok := basePriceLarge[family]
if !ok {
base = 0.10 // <-- a GPU box priced like a t3
}
multiplier, ok := sizeMultiplier[size]
if !ok {
multiplier = 2.0
}
return base * multiplier
0.10 × 8.0 = $0.80 for g7e.4xlarge. And staticOnDemandPricer documents the
consequence explicitly (truffle/pkg/aws/pricing.go:262):
// It never errors: unknown types fall through to a family-based estimate.
So the fabricated value is indistinguishable from a real one at every call site.
Why this is worse than a stale table
The estimate is structurally wrong for GPUs, not merely out of date. The
fallback extrapolates from vCPU count, but a GPU instance's price is dominated by
the accelerator, so no amount of table refreshing fixes the unknown-family path
— every future GPU generation will land on $0.10 × multiplier on the day it
launches. p6-b200.48xlarge is off by 11.9× today; a hypothetical p7 would be
off by more.
The p5e.48xlarge row is a distinct and arguably worse failure: AWS publishes
zero on-demand Price List rows for it (it is Capacity-Block-only — see
truffle#109). The correct answer is "there is no on-demand price"; returning
$9.60 means a caller will happily recommend and cost-model an instance that
cannot be purchased on demand at all.
Impact through truffle
truffle/pkg/aws/pricing.go:137 makes this the production default:
func newDefaultOnDemandPricer(cfg aws.Config) OnDemandPricer {
return &fallbackPricer{
primary: newAWSOnDemandPricer(cfg),
fallback: staticOnDemandPricer{},
}
}
The fallback is well-intentioned (no creds, no network, emulator without a Price
List endpoint), but because it never errors, a throttled or credential-less run
degrades from "no price" to "confidently wrong price" — and truffle is the
pricing authority the rest of the suite consumes.
This is the fourth instance of the same pattern in the suite, in a third
location: spawn#447 (pkg/slurm hardcoded p4d/p5 prices, off 49% and 79%), the
hardcoded SageMaker premium, the hardcoded Capacity Block discount, and now this.
That recurrence is the actual argument for the fix below being structural rather
than another table refresh.
Suggested fix
- Make the unknown-family case fail, not guess. Return
(0, ErrPriceUnknown) from estimatePriceByFamily when the family isn't in
the table, and let staticOnDemandPricer propagate it. Callers can then
render "unpriced" instead of a fake number. This is the load-bearing change;
everything else is optional.
- Add the missing families: g7, g7e, g6e, p5, p5e, p5en, p6-b200, p6-b300.
Happy to supply live-verified values for the US regions.
- Never estimate accelerator instances. Even with the table updated, gate
the estimator so any family whose types carry a GPU/accelerator returns
ErrPriceUnknown rather than a vCPU-derived guess.
- Represent "no on-demand price exists" distinctly from "we don't know" so
Capacity-Block-only types like p5e.48xlarge can be reported honestly.
- Add a CI check that flags table entries older than N months, and a test
asserting unknown families error rather than return a number.
If you'd prefer, I can send a PR for (1) + (3) — they're small and they're what
actually stops the silent-wrong-number class. (2) is a data refresh that will
drift again, which is why I'd rather not have it be the only fix.
Related: truffle#107, truffle#108, truffle#109, spawn#447.
Summary
pricing/ec2.gohas no entries for any GPU family released since ~2024, andestimatePriceByFamilycovers the gap by silently inventing a price andreturning
err == nil. For modern GPU instances the invented number is 4–12×below the real rate, and for at least one type it fabricates a price where AWS
publishes none at all.
Found while building
cultivar(a HuggingFace→AWS inference deployer whose entireoutput is a cost recommendation). Verified live against the AWS Price List API in
account 942542972736, us-east-1, 2026-07-27.
Evidence
GetEC2HourlyRate("us-east-1", …)vs live Price List on-demand(
operatingSystem=Linux,tenancy=Shared,preInstalledSw=NA,capacitystatus=Used):GetEC2HourlyRateReproducer:
Root cause
basePriceLargeinpricing/ec2.gohas nog7,g7e,g6e,p5,p5e,p6-b200, orp6-b300key.estimatePriceByFamily(line 183) then does:0.10 × 8.0 = $0.80forg7e.4xlarge. AndstaticOnDemandPricerdocuments theconsequence explicitly (
truffle/pkg/aws/pricing.go:262):So the fabricated value is indistinguishable from a real one at every call site.
Why this is worse than a stale table
The estimate is structurally wrong for GPUs, not merely out of date. The
fallback extrapolates from vCPU count, but a GPU instance's price is dominated by
the accelerator, so no amount of table refreshing fixes the unknown-family path
— every future GPU generation will land on
$0.10 × multiplieron the day itlaunches.
p6-b200.48xlargeis off by 11.9× today; a hypotheticalp7would beoff by more.
The
p5e.48xlargerow is a distinct and arguably worse failure: AWS publisheszero on-demand Price List rows for it (it is Capacity-Block-only — see
truffle#109). The correct answer is "there is no on-demand price"; returning
$9.60 means a caller will happily recommend and cost-model an instance that
cannot be purchased on demand at all.
Impact through truffle
truffle/pkg/aws/pricing.go:137makes this the production default:The fallback is well-intentioned (no creds, no network, emulator without a Price
List endpoint), but because it never errors, a throttled or credential-less run
degrades from "no price" to "confidently wrong price" — and truffle is the
pricing authority the rest of the suite consumes.
This is the fourth instance of the same pattern in the suite, in a third
location: spawn#447 (
pkg/slurmhardcoded p4d/p5 prices, off 49% and 79%), thehardcoded SageMaker premium, the hardcoded Capacity Block discount, and now this.
That recurrence is the actual argument for the fix below being structural rather
than another table refresh.
Suggested fix
(0, ErrPriceUnknown)fromestimatePriceByFamilywhen the family isn't inthe table, and let
staticOnDemandPricerpropagate it. Callers can thenrender "unpriced" instead of a fake number. This is the load-bearing change;
everything else is optional.
Happy to supply live-verified values for the US regions.
the estimator so any family whose types carry a GPU/accelerator returns
ErrPriceUnknownrather than a vCPU-derived guess.Capacity-Block-only types like p5e.48xlarge can be reported honestly.
asserting unknown families error rather than return a number.
If you'd prefer, I can send a PR for (1) + (3) — they're small and they're what
actually stops the silent-wrong-number class. (2) is a data refresh that will
drift again, which is why I'd rather not have it be the only fix.
Related: truffle#107, truffle#108, truffle#109, spawn#447.