diff --git a/app/igneum-app/tiers/class-v5-tiers.json b/app/igneum-app/tiers/class-v5-tiers.json new file mode 100644 index 000000000..5827e9b55 --- /dev/null +++ b/app/igneum-app/tiers/class-v5-tiers.json @@ -0,0 +1,1727 @@ +{ + "_about": "Ember Tune tiers per card class under class v5 (floor lane 4, the honest denominator, 8 October 2026; docs/analysis/class-v6/floor/denominator.md). One entry per card class: the three tiers (efficiency, balanced, max) as rows in the shape the app reads back from a card's state (src/ember.rs tier_from_json: id, clock_mhz, power_pct, mem_mhz, limit_w, mhs, w; mhw and source as Tier::json writes them), plus the fields this table adds: label (measured or estimated), uj (microjoules per hash at that point under class v5), note and src. clock_mhz 0 = unlocked; power_pct is percent of the card's default limit (50 to 100); AMD rows carry the ADLX offsets in core_offset_mhz and power_offset_pct instead (the app's AMD knob, 0.3.25). A card with no lever (Apple, Intel) has one tier, max, source stock. Nothing here is interpolated between measured rows: an estimated row is a prior the card's own search starts from and replaces; a measured row is the search's own result on that card.", + "class": "v5", + "class_string": "mx8-era+sh256x27+state (class v4's shape, 256 x 27 shadow, plus the state leaves)", + "v5_over_v4": { + "rate_pct": 0.0, + "watts_pct": 2.0, + "source": "the hash lane's v5lock job, 8 October 2026: at the 5090's 1,300 MHz knee class v5 125.93 MH/s at 299.8 W against class v4 125.92 at 294.0 W" + }, + "tier_ids": [ + "efficiency", + "balanced", + "max" + ], + "tier_rule": "efficiency = the best MH per watt row of the card's search outright (the knee or one rung past it); balanced = the best MH per watt row within 1 percent of the top rate (the default from install); max = the stock row (unlocked, 100 percent). The search measures; this table is where it starts and what it ships when it cannot run.", + "remeasure_rule": { + "stale_when": "the card's program class differs from the tiers' class (src/ember.rs tiers_stale): a set measured under class v4 never applies as current under class v5", + "on_flip": "the stored efficiency and balanced points are the provisional start of the re-measure, not its result: the search begins at the stored point and walks one rung either way (clock and power), the fingerprint checked on every step; the max tier is stock and is never stale", + "measured_flip": "v4 to v5 on the RTX 5090 at the 1,300 MHz lock: 0.0 percent of rate, +2.0 percent of watts (the v5lock job); the knee did not move", + "expected_flip_cost": "any class change that keeps the shadow's op count and mix inside the latency-bound band moves the knee by 0 to 1 rung; a change that doubles the op count moves the knee up (the 5090 at 431 W binds at 210,000 ops), so a class whose shadow leaves the band re-measures from stock, not from the stored point", + "period": "the stored set also re-measures weekly (PERIOD_S) and on a driver major change (the prior key carries driver major and class)" + }, + "cards": [ + { + "card": "NVIDIA GeForce RTX 5090", + "match": [ + "5090" + ], + "vendor": "nvidia", + "arch": "blackwell", + "memory_gb": 32, + "label": "measured", + "stock": { + "mhs": 136.8, + "w": 475.5, + "uj": 3.48, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1200, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 575, + "mhs": 133.8, + "w": 311.2, + "mhw": 0.4299, + "source": "measured", + "label": "measured", + "uj": 2.33, + "note": "class v4 best MH per watt (133.80 MH/s at 305.1 W, 2.2 percent of rate given); under class v5 +2.0 percent watts: 311.2 W, 2.33" + }, + { + "id": "balanced", + "clock_mhz": 1300, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 575, + "mhs": 134.76, + "w": 318.8, + "mhw": 0.4227, + "source": "measured", + "label": "measured", + "uj": 2.37, + "note": "the knee: class v4 134.76 MH/s at 312.5 W on the efficiency pass; under class v5 +2.0 percent watts: 318.8 W, 2.37 (the v5lock job read class v5 125.93 at 299.8 W with the one-warp-per-block worker: 2.38, the same energy at a lower rate)" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 575, + "mhs": 136.8, + "w": 475.5, + "mhw": 0.2877, + "source": "stock", + "label": "measured", + "uj": 3.48, + "note": "the power cap is flat on this hash (0.41 MH/W under every limit), so no power rung is used on any tier" + } + ], + "src": "docs/bench-log.md, the class v4 efficiency passes 7 to 8 October 2026 (PC 1); counter-asic-4-research.md 20.3" + }, + { + "card": "NVIDIA GeForce RTX 5080", + "match": [ + "5080" + ], + "vendor": "nvidia", + "arch": "blackwell", + "memory_gb": 16, + "label": "measured", + "stock": { + "mhs": 71.41, + "w": 253.1, + "uj": 3.54, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1100, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 360, + "mhs": 71.2, + "w": 146.6, + "mhw": 0.4857, + "source": "measured", + "label": "measured", + "uj": 2.06, + "note": "class v4 best MH per watt; the draw floors from 1,500 MHz down; the knee is between 1,000 and 900 MHz (900 costs 5.2 percent); class v5 about 2.10" + }, + { + "id": "balanced", + "clock_mhz": 1100, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 360, + "mhs": 71.2, + "w": 146.6, + "mhw": 0.4857, + "source": "measured", + "label": "measured", + "uj": 2.06, + "note": "the same row: the rate at 1,100 MHz is within 0.3 percent of unlocked" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 360, + "mhs": 71.41, + "w": 253.1, + "mhw": 0.2821, + "source": "stock", + "label": "measured", + "uj": 3.54, + "note": "PC 1 (Windows) stock; a rented Linux host read 143.4 W at the same rate on class v3 against 169.7 W here: the two methods differ and neither is corrected" + } + ], + "src": "docs/bench-log.md, the RTX 5080 ladder, 8 October 2026 00:45 to 01:54 UTC (PC 1)" + }, + { + "card": "NVIDIA GeForce RTX 5070 Ti", + "match": [ + "5070 TI", + "5070TI" + ], + "vendor": "nvidia", + "arch": "blackwell", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 78.8, + "w": 224.0, + "uj": 2.84, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1100, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 300, + "mhs": 78.7, + "w": 131.0, + "mhw": 0.6008, + "source": "measured", + "label": "estimated", + "uj": 1.7, + "note": "the Blackwell shape applied to the measured stock rows (the v3 draw x0.63, the premium x0.5): band 1.6 to 2.0; the rented host refused -lgc, so the knee is the search's to find" + }, + { + "id": "balanced", + "clock_mhz": 1300, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 300, + "mhs": 78.8, + "w": 140.0, + "mhw": 0.5629, + "source": "measured", + "label": "estimated", + "uj": 1.81, + "note": "one rung above the efficiency point" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 300, + "mhs": 78.8, + "w": 224.0, + "mhw": 0.3518, + "source": "stock", + "label": "measured", + "uj": 2.84, + "note": "rented pod 8 October 10:09Z: class v3 78.69 MH/s at 140.8 W, class v4 78.78 at 224.0 W" + } + ], + "src": "docs/design/class-v6-rotating-family.md section 0 (the 5070 Ti column); the knee modelled on the 5080 and 5090 ladders" + }, + { + "card": "NVIDIA GeForce RTX 5070", + "match": [ + "5070" + ], + "vendor": "nvidia", + "arch": "blackwell", + "memory_gb": 12, + "label": "estimated", + "stock": { + "mhs": 58.9, + "w": 176.1, + "uj": 2.99, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1100, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 250, + "mhs": 58.9, + "w": 103.0, + "mhw": 0.5718, + "source": "measured", + "label": "estimated", + "uj": 1.75, + "note": "the Blackwell shape on the measured stock rows (paired class v3 113 W): band 1.7 to 2.1" + }, + { + "id": "balanced", + "clock_mhz": 1300, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 250, + "mhs": 58.9, + "w": 110.0, + "mhw": 0.5355, + "source": "measured", + "label": "estimated", + "uj": 1.87, + "note": "one rung above the efficiency point" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 250, + "mhs": 58.9, + "w": 176.1, + "mhw": 0.3344, + "source": "stock", + "label": "measured", + "uj": 2.99, + "note": "rented v4watts pod 8 October; the 7 October pod read class v3 52.0 MH/s at 102.8 W (the rate differs by host)" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, the 8 October v4watts rows" + }, + { + "card": "NVIDIA GeForce RTX 5060 Ti", + "match": [ + "5060 TI", + "5060TI" + ], + "vendor": "nvidia", + "arch": "blackwell", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 30.9, + "w": 114.8, + "uj": 3.72, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1100, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 180, + "mhs": 30.9, + "w": 72.0, + "mhw": 0.4292, + "source": "measured", + "label": "estimated", + "uj": 2.33, + "note": "the Blackwell shape on the PC 2 stock row: band 2.2 to 2.6; the card-in job on PC 2 measures the ladder" + }, + { + "id": "balanced", + "clock_mhz": 1300, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 180, + "mhs": 30.9, + "w": 78.0, + "mhw": 0.3962, + "source": "measured", + "label": "estimated", + "uj": 2.57, + "note": "one rung above the efficiency point" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 180, + "mhs": 30.9, + "w": 114.8, + "mhw": 0.2692, + "source": "stock", + "label": "measured", + "uj": 3.72, + "note": "PC 2, the Thunderbolt enclosure, 7 October: 30.9 MH/s over 10 minutes at 114.8 W (class v4 sub-version 1)" + } + ], + "src": "docs/bench-log.md, the RTX 5060 Ti entry, 7 October 2026" + }, + { + "card": "NVIDIA GeForce RTX 5060", + "match": [ + "5060" + ], + "vendor": "nvidia", + "arch": "blackwell", + "memory_gb": 8, + "label": "estimated", + "stock": { + "mhs": 31.27, + "w": 110.0, + "uj": 3.52, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1100, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 145, + "mhs": 31.27, + "w": 67.0, + "mhw": 0.4667, + "source": "measured", + "label": "estimated", + "uj": 2.15, + "note": "class v3 measured 31.27 MH/s at 75.4 W; the class v4 stock and the knee by the Blackwell shape: band 2.0 to 2.5" + }, + { + "id": "balanced", + "clock_mhz": 1300, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 145, + "mhs": 31.27, + "w": 72.0, + "mhw": 0.4343, + "source": "measured", + "label": "estimated", + "uj": 2.3, + "note": "one rung above the efficiency point" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 145, + "mhs": 31.27, + "w": 110.0, + "mhw": 0.2843, + "source": "stock", + "label": "estimated", + "uj": 3.52, + "note": "class v4 stock modelled from the measured class v3 row plus the Blackwell premium per op" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 7 October (class v3); the class v4 row owed to the denominator sweep" + }, + { + "card": "NVIDIA GeForce RTX 4090", + "match": [ + "4090" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 24, + "label": "estimated", + "stock": { + "mhs": 63.0, + "w": 280.0, + "uj": 4.44, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 60, + "mem_mhz": 0, + "limit_w": 450, + "mhs": 63.0, + "w": 210.0, + "mhw": 0.3, + "source": "measured", + "label": "estimated", + "uj": 3.4, + "note": "the Ada shape (the 4070's measured tune: v3 x0.72, the premium x0.7): band 3.0 to 3.8; the class v5 kit read 62.76 MH/s on a rented 4090 with the watts unread" + }, + { + "id": "balanced", + "clock_mhz": 2400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 450, + "mhs": 63.0, + "w": 235.0, + "mhw": 0.2681, + "source": "measured", + "label": "estimated", + "uj": 3.73, + "note": "the Ada prior (a cap holds the rate until the SM clock falls under about 2,400 MHz)" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 450, + "mhs": 63.0, + "w": 280.0, + "mhw": 0.225, + "source": "stock", + "label": "estimated", + "uj": 4.44, + "note": "the 0.3.12 rented row 52.25 MH/s at 183.1 W and the rig's 57.4 at 205 W were an earlier class; the class v4 stock row is owed to the denominator sweep" + } + ], + "src": "docs/analysis/prover-tiers-real-cards.md (6 October); ~/igneum-fleet/cv5-results.txt (the class v5 kit on a 4090)" + }, + { + "card": "NVIDIA GeForce RTX 4080", + "match": [ + "4080" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 40.74, + "w": 185.6, + "uj": 4.56, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 60, + "mem_mhz": 0, + "limit_w": 320, + "mhs": 40.7, + "w": 133.0, + "mhw": 0.3068, + "source": "measured", + "label": "estimated", + "uj": 3.33, + "note": "the Ada shape on the measured stock rows (class v3 128.6 W, class v4 185.6 W): band 3.1 to 3.7; the 4080 Super reads the same (134.9 / 195.7 W at 42.6 MH/s)" + }, + { + "id": "balanced", + "clock_mhz": 2400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 320, + "mhs": 40.74, + "w": 150.0, + "mhw": 0.2716, + "source": "measured", + "label": "estimated", + "uj": 3.68, + "note": "the Ada prior" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 320, + "mhs": 40.74, + "w": 185.6, + "mhw": 0.2195, + "source": "stock", + "label": "measured", + "uj": 4.56, + "note": "rented pods 8 October (class v3 and the v4watts row)" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 8 October" + }, + { + "card": "NVIDIA GeForce RTX 4070", + "match": [ + "4070" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 12, + "label": "measured", + "stock": { + "mhs": 28.7, + "w": 102.7, + "uj": 3.58, + "class": "v3" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 50, + "mem_mhz": 0, + "limit_w": 200, + "mhs": 31.08, + "w": 109.0, + "mhw": 0.2851, + "source": "measured", + "label": "measured", + "uj": 3.51, + "note": "Ember run 6's tune point (1,863 MHz at 50 percent) under class v4 (sh256x27): 31.08 MH/s at 109.0 W, +30 W over class v3 at the same point for no rate; class v5 about 3.58; the ladder below 1,860 under class v4 is owed (band 3.2 to 3.6)" + }, + { + "id": "balanced", + "clock_mhz": 2173, + "power_pct": 50, + "mem_mhz": 0, + "limit_w": 200, + "mhs": 31.1, + "w": 111.0, + "mhw": 0.2802, + "source": "measured", + "label": "estimated", + "uj": 3.57, + "note": "Ember run 6's 2,173 MHz rung (77.7 W on class v3) plus the measured 30 W class v4 premium" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 200, + "mhs": 28.72, + "w": 106.0, + "mhw": 0.2709, + "source": "stock", + "label": "measured", + "uj": 3.69, + "note": "Ember run 6 stock on class v3 (28.72 MH/s at 106.0 W); the bench-log 5 October row 28.7 at 102.7 W (3.58)" + } + ], + "src": "docs/plans/counter-asic-3-status.md item 8 (the 4070 rows, 6 October); docs/bench-log.md Ember run 6" + }, + { + "card": "NVIDIA GeForce RTX 4070 Ti", + "match": [ + "4070 TI", + "4070TI" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 12, + "label": "estimated", + "stock": { + "mhs": 31.26, + "w": 155.3, + "uj": 4.97, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 60, + "mem_mhz": 0, + "limit_w": 285, + "mhs": 31.26, + "w": 111.0, + "mhw": 0.2816, + "source": "measured", + "label": "estimated", + "uj": 3.6, + "note": "the Ada shape on the measured stock rows (class v3 95.4 W, class v4 155.3 W); the 4070 Super at a 110 W host cap held 31.26 MH/s at 108.4 W under class v4 (3.47): a cap row measured on a sibling" + }, + { + "id": "balanced", + "clock_mhz": 2400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 285, + "mhs": 31.26, + "w": 125.0, + "mhw": 0.2501, + "source": "measured", + "label": "estimated", + "uj": 4.08, + "note": "the Ada prior" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 285, + "mhs": 31.26, + "w": 155.3, + "mhw": 0.2013, + "source": "stock", + "label": "measured", + "uj": 4.97, + "note": "rented pods 8 October" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 8 October" + }, + { + "card": "NVIDIA GeForce RTX 4060 Ti", + "match": [ + "4060 TI", + "4060TI" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 20.1, + "w": 102.3, + "uj": 5.09, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 60, + "mem_mhz": 0, + "limit_w": 165, + "mhs": 20.1, + "w": 73.0, + "mhw": 0.2753, + "source": "measured", + "label": "estimated", + "uj": 3.7, + "note": "the Ada shape on the measured stock rows (class v3 79.2 W, class v4 102.3 W); the 8 GB card reads the same on class v3 (20.10 MH/s at 77.5 W)" + }, + { + "id": "balanced", + "clock_mhz": 2400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 165, + "mhs": 20.1, + "w": 82.0, + "mhw": 0.2451, + "source": "measured", + "label": "estimated", + "uj": 4.16, + "note": "the Ada prior" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 165, + "mhs": 20.1, + "w": 102.3, + "mhw": 0.1965, + "source": "stock", + "label": "measured", + "uj": 5.09, + "note": "rented pods 7 and 8 October" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 7 and 8 October" + }, + { + "card": "NVIDIA GeForce RTX 4060", + "match": [ + "4060" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 8, + "label": "estimated", + "stock": { + "mhs": 19.09, + "w": 95.0, + "uj": 4.98, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 60, + "mem_mhz": 0, + "limit_w": 115, + "mhs": 19.09, + "w": 72.0, + "mhw": 0.2651, + "source": "measured", + "label": "estimated", + "uj": 3.77, + "note": "19.09 MH/s measured on three rented hosts, the watts unread on every one (no power sensor): the watts are the 4060 Ti's shape; band 3.7 to 4.2" + }, + { + "id": "balanced", + "clock_mhz": 2400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 115, + "mhs": 19.09, + "w": 80.0, + "mhw": 0.2386, + "source": "measured", + "label": "estimated", + "uj": 4.19, + "note": "the Ada prior" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 115, + "mhs": 19.09, + "w": 95.0, + "mhw": 0.2009, + "source": "stock", + "label": "estimated", + "uj": 4.98, + "note": "watts OWED: every rented 4060 host exposed no power sensor" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 7 and 8 October (the rate); the watts modelled" + }, + { + "card": "NVIDIA GeForce RTX 3090", + "match": [ + "3090" + ], + "vendor": "nvidia", + "arch": "ampere", + "memory_gb": 24, + "label": "estimated", + "stock": { + "mhs": 50.0, + "w": 360.0, + "uj": 7.2, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1800, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 350, + "mhs": 50.0, + "w": 320.0, + "mhw": 0.1563, + "source": "measured", + "label": "estimated", + "uj": 6.4, + "note": "the Ampere lever is the power cap and small: the knee sits near 1,800 MHz against a 1,900 to 2,000 MHz stock clock (10 to 15 percent of the draw); a cap that takes the SM under 1,800 costs rate (the 3080 Ti at 63 percent lost 24 to 28 percent); the 3090 Ti read class v3 61.95 MH/s at 249.5 W (4.03)" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 90, + "mem_mhz": 0, + "limit_w": 350, + "mhs": 50.0, + "w": 340.0, + "mhw": 0.1471, + "source": "measured", + "label": "estimated", + "uj": 6.8, + "note": "one power rung under stock, no lock" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 350, + "mhs": 50.0, + "w": 360.0, + "mhw": 0.1389, + "source": "stock", + "label": "estimated", + "uj": 7.2, + "note": "the 0.3.12 rented row 37.79 MH/s at 228.8 W was an earlier class; the standing voters read 42 to 50 MH/s; the class v3 and v4 stock rows are owed to the denominator sweep" + } + ], + "src": "docs/analysis/prover-tiers-real-cards.md; site/miner-bench.json; the sweep pending" + }, + { + "card": "NVIDIA GeForce RTX 3080", + "match": [ + "3080" + ], + "vendor": "nvidia", + "arch": "ampere", + "memory_gb": 10, + "label": "estimated", + "stock": { + "mhs": 42.0, + "w": 270.0, + "uj": 6.43, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1800, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 320, + "mhs": 42.0, + "w": 240.0, + "mhw": 0.175, + "source": "measured", + "label": "estimated", + "uj": 5.7, + "note": "the Ampere cap lever (10 to 15 percent); the 3080 Ti measured class v3 58.9 MH/s at 293.5 W (4.98) and, at a 220 W host cap with the SM at 749 MHz, class v4 42.6 at 216 W (5.08) with 28 percent of the rate lost: the Ampere cap's known-failed row" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 90, + "mem_mhz": 0, + "limit_w": 320, + "mhs": 42.0, + "w": 255.0, + "mhw": 0.1647, + "source": "measured", + "label": "estimated", + "uj": 6.07, + "note": "one power rung under stock" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 320, + "mhs": 42.0, + "w": 270.0, + "mhw": 0.1556, + "source": "stock", + "label": "estimated", + "uj": 6.43, + "note": "the 0.3.12 rented row 40.82 MH/s at 204.9 W was an earlier class; the class v3 and v4 stock rows are owed to the denominator sweep" + } + ], + "src": "docs/analysis/prover-tiers-real-cards.md; ~/igneum-fleet/cardbench/rows.jsonl (the 3080 Ti rows); the sweep pending" + }, + { + "card": "NVIDIA GeForce RTX 3070", + "match": [ + "3070" + ], + "vendor": "nvidia", + "arch": "ampere", + "memory_gb": 8, + "label": "estimated", + "stock": { + "mhs": 37.11, + "w": 196.4, + "uj": 5.29, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1800, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 220, + "mhs": 37.1, + "w": 178.0, + "mhw": 0.2084, + "source": "measured", + "label": "estimated", + "uj": 4.8, + "note": "the Ampere cap lever on the measured stock rows (class v3 142.3 W, class v4 196.4 W); the 3070 Ti 38.98 MH/s at 177.1 / 267.6 W" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 90, + "mem_mhz": 0, + "limit_w": 220, + "mhs": 37.11, + "w": 186.0, + "mhw": 0.1995, + "source": "measured", + "label": "estimated", + "uj": 5.01, + "note": "one power rung under stock" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 220, + "mhs": 37.11, + "w": 196.4, + "mhw": 0.189, + "source": "stock", + "label": "measured", + "uj": 5.29, + "note": "rented pods 8 October" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 8 October" + }, + { + "card": "NVIDIA GeForce RTX 3060", + "match": [ + "3060" + ], + "vendor": "nvidia", + "arch": "ampere", + "memory_gb": 12, + "label": "estimated", + "stock": { + "mhs": 26.89, + "w": 145.0, + "uj": 5.39, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1800, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 170, + "mhs": 26.89, + "w": 132.0, + "mhw": 0.2037, + "source": "measured", + "label": "estimated", + "uj": 4.9, + "note": "class v3 measured 26.89 MH/s at 111.6 W; the class v4 stock by the Ampere premium; the 3060 Ti at a 130 W host cap held 33.06 MH/s at 128.7 W under class v4 (3.89): a cap row measured on a sibling" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 90, + "mem_mhz": 0, + "limit_w": 170, + "mhs": 26.89, + "w": 138.0, + "mhw": 0.1949, + "source": "measured", + "label": "estimated", + "uj": 5.13, + "note": "one power rung under stock" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 170, + "mhs": 26.89, + "w": 145.0, + "mhw": 0.1854, + "source": "stock", + "label": "estimated", + "uj": 5.39, + "note": "the class v4 stock row is owed to the denominator sweep" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 7 October (class v3); the sweep pending" + }, + { + "card": "AMD Radeon RX 9070 XT", + "match": [ + "9070 XT", + "9070XT", + "GFX1201" + ], + "vendor": "amd", + "arch": "rdna4", + "memory_gb": 16, + "label": "measured", + "stock": { + "mhs": 18.9, + "w": 202.0, + "uj": 10.7, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 0, + "power_pct": 70, + "mem_mhz": 0, + "core_offset_mhz": -500, + "power_offset_pct": -30, + "limit_w": 304, + "mhs": 18.9, + "w": 149.3, + "mhw": 0.1266, + "source": "measured", + "label": "measured", + "uj": 7.9, + "note": "the ADLX grid (24 rows, PC 1): the rate flat at 18.9 MH/s across the grid, the best point -500 MHz core and -30 percent power, 24 percent under stock; class v5 about 8.1" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 85, + "mem_mhz": 0, + "core_offset_mhz": -300, + "power_offset_pct": -15, + "limit_w": 304, + "mhs": 18.9, + "w": 172.0, + "mhw": 0.1099, + "source": "measured", + "label": "estimated", + "uj": 9.1, + "note": "inside the flat band, half the offsets" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "core_offset_mhz": 0, + "power_offset_pct": 0, + "limit_w": 304, + "mhs": 18.9, + "w": 202.0, + "mhw": 0.0936, + "source": "stock", + "label": "measured", + "uj": 10.7, + "note": "the app's own power reading at the stock point, 8 October 07:11 UK" + } + ], + "src": "the hash lane's PC 1 AMD grid, 8 October 2026 12:13 BST; site/miner-bench.json" + }, + { + "card": "AMD Radeon RX 7900 XTX", + "match": [ + "7900 XTX", + "7900XTX" + ], + "vendor": "amd", + "arch": "rdna3", + "memory_gb": 24, + "label": "estimated", + "stock": { + "mhs": 28.0, + "w": 300.0, + "uj": 10.7, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 0, + "power_pct": 70, + "mem_mhz": 0, + "core_offset_mhz": -500, + "power_offset_pct": -30, + "limit_w": 355, + "mhs": 28.0, + "w": 224.0, + "mhw": 0.125, + "source": "measured", + "label": "estimated", + "uj": 8.0, + "note": "not rentable, not owned: the 9070 XT's dependent-read rate per channel (150 M per second per 32-bit channel) on 24 channels; the knob by the 9070 XT grid" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 85, + "mem_mhz": 0, + "core_offset_mhz": -300, + "power_offset_pct": -15, + "limit_w": 355, + "mhs": 28.0, + "w": 255.0, + "mhw": 0.1098, + "source": "measured", + "label": "estimated", + "uj": 9.1, + "note": "half the offsets" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "core_offset_mhz": 0, + "power_offset_pct": 0, + "limit_w": 355, + "mhs": 28.0, + "w": 300.0, + "mhw": 0.0933, + "source": "stock", + "label": "estimated", + "uj": 10.7, + "note": "modelled" + } + ], + "src": "modelled on the 9070 XT rows" + }, + { + "card": "AMD Radeon RX 7800 XT", + "match": [ + "7800 XT", + "7800XT" + ], + "vendor": "amd", + "arch": "rdna3", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 18.0, + "w": 230.0, + "uj": 12.8, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 0, + "power_pct": 70, + "mem_mhz": 0, + "core_offset_mhz": -500, + "power_offset_pct": -30, + "limit_w": 263, + "mhs": 18.0, + "w": 171.0, + "mhw": 0.1053, + "source": "measured", + "label": "estimated", + "uj": 9.5, + "note": "16 channels of GDDR6 at the 9070 XT's per-channel rate; the knob by the 9070 XT grid" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 85, + "mem_mhz": 0, + "core_offset_mhz": -300, + "power_offset_pct": -15, + "limit_w": 263, + "mhs": 18.0, + "w": 195.0, + "mhw": 0.0923, + "source": "measured", + "label": "estimated", + "uj": 10.8, + "note": "half the offsets" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "core_offset_mhz": 0, + "power_offset_pct": 0, + "limit_w": 263, + "mhs": 18.0, + "w": 230.0, + "mhw": 0.0783, + "source": "stock", + "label": "estimated", + "uj": 12.8, + "note": "modelled" + } + ], + "src": "modelled on the 9070 XT rows" + }, + { + "card": "AMD Radeon RX 7600 XT", + "match": [ + "7600 XT", + "7600XT" + ], + "vendor": "amd", + "arch": "rdna3", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 9.0, + "w": 160.0, + "uj": 17.8, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 0, + "power_pct": 70, + "mem_mhz": 0, + "core_offset_mhz": -500, + "power_offset_pct": -30, + "limit_w": 190, + "mhs": 9.0, + "w": 117.0, + "mhw": 0.0769, + "source": "measured", + "label": "estimated", + "uj": 13.0, + "note": "8 channels; the card-in queue holds one for a PC measurement" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 85, + "mem_mhz": 0, + "core_offset_mhz": -300, + "power_offset_pct": -15, + "limit_w": 190, + "mhs": 9.0, + "w": 136.0, + "mhw": 0.0662, + "source": "measured", + "label": "estimated", + "uj": 15.1, + "note": "half the offsets" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "core_offset_mhz": 0, + "power_offset_pct": 0, + "limit_w": 190, + "mhs": 9.0, + "w": 160.0, + "mhw": 0.0563, + "source": "stock", + "label": "estimated", + "uj": 17.8, + "note": "modelled" + } + ], + "src": "modelled on the 9070 XT rows" + }, + { + "card": "AMD Radeon RX 9060 XT", + "match": [ + "9060 XT", + "9060XT" + ], + "vendor": "amd", + "arch": "rdna4", + "memory_gb": 16, + "label": "estimated", + "stock": { + "mhs": 9.5, + "w": 130.0, + "uj": 13.7, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 0, + "power_pct": 70, + "mem_mhz": 0, + "core_offset_mhz": -500, + "power_offset_pct": -30, + "limit_w": 160, + "mhs": 9.5, + "w": 95.0, + "mhw": 0.1, + "source": "measured", + "label": "estimated", + "uj": 10.0, + "note": "8 channels of GDDR6 on RDNA 4; the card-in queue holds one" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 85, + "mem_mhz": 0, + "core_offset_mhz": -300, + "power_offset_pct": -15, + "limit_w": 160, + "mhs": 9.5, + "w": 110.0, + "mhw": 0.0864, + "source": "measured", + "label": "estimated", + "uj": 11.6, + "note": "half the offsets" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "core_offset_mhz": 0, + "power_offset_pct": 0, + "limit_w": 160, + "mhs": 9.5, + "w": 130.0, + "mhw": 0.0731, + "source": "stock", + "label": "estimated", + "uj": 13.7, + "note": "modelled" + } + ], + "src": "modelled on the 9070 XT rows" + }, + { + "card": "NVIDIA H100 80GB HBM3", + "match": [ + "H100" + ], + "vendor": "nvidia", + "arch": "hopper", + "memory_gb": 80, + "label": "estimated", + "stock": { + "mhs": 250.6, + "w": 695.1, + "uj": 2.77, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 700, + "mhs": 250.0, + "w": 500.0, + "mhw": 0.5, + "source": "measured", + "label": "estimated", + "uj": 2.0, + "note": "the premium 283 W at stock (11.2 pJ per op); a lock on an owned host (Hopper at 1,980 MHz boost, HBM3-bound): band 1.9 to 2.4; rented hosts refuse -lgc" + }, + { + "id": "balanced", + "clock_mhz": 1600, + "power_pct": 90, + "mem_mhz": 0, + "limit_w": 700, + "mhs": 250.6, + "w": 560.0, + "mhw": 0.4475, + "source": "measured", + "label": "estimated", + "uj": 2.23, + "note": "one rung above" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 700, + "mhs": 250.6, + "w": 695.1, + "mhw": 0.3605, + "source": "stock", + "label": "measured", + "uj": 2.77, + "note": "rented pods 7 and 8 October (class v3 248.9 MH/s at 411.7 W; class v4 250.6 at 695.1 W)" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 8 October" + }, + { + "card": "NVIDIA L40S", + "match": [ + "L40S" + ], + "vendor": "nvidia", + "arch": "ada", + "memory_gb": 48, + "label": "estimated", + "stock": { + "mhs": 56.4, + "w": 277.6, + "uj": 4.92, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1860, + "power_pct": 60, + "mem_mhz": 0, + "limit_w": 350, + "mhs": 56.4, + "w": 198.0, + "mhw": 0.2848, + "source": "measured", + "label": "estimated", + "uj": 3.5, + "note": "the Ada shape on the measured stock rows (class v3 220.7 W, class v4 277.6 W)" + }, + { + "id": "balanced", + "clock_mhz": 2400, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 350, + "mhs": 56.4, + "w": 225.0, + "mhw": 0.2507, + "source": "measured", + "label": "estimated", + "uj": 3.99, + "note": "the Ada prior" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 350, + "mhs": 56.4, + "w": 277.6, + "mhw": 0.2032, + "source": "stock", + "label": "measured", + "uj": 4.92, + "note": "rented pods 8 October" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 8 October" + }, + { + "card": "NVIDIA A100-SXM4-80GB", + "match": [ + "A100" + ], + "vendor": "nvidia", + "arch": "ampere", + "memory_gb": 80, + "label": "estimated", + "stock": { + "mhs": 138.0, + "w": 489.3, + "uj": 3.54, + "class": "v4" + }, + "tiers": [ + { + "id": "efficiency", + "clock_mhz": 1200, + "power_pct": 80, + "mem_mhz": 0, + "limit_w": 400, + "mhs": 138.0, + "w": 430.0, + "mhw": 0.3209, + "source": "measured", + "label": "estimated", + "uj": 3.1, + "note": "the premium 221 W at stock (15.8 pJ per op, the dearest ALU in the record); the card already runs at 1,410 MHz, so a cap recovers about 60 W" + }, + { + "id": "balanced", + "clock_mhz": 0, + "power_pct": 90, + "mem_mhz": 0, + "limit_w": 400, + "mhs": 138.0, + "w": 460.0, + "mhw": 0.3, + "source": "measured", + "label": "estimated", + "uj": 3.33, + "note": "one power rung under stock" + }, + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 400, + "mhs": 138.0, + "w": 489.3, + "mhw": 0.282, + "source": "stock", + "label": "measured", + "uj": 3.54, + "note": "rented pods 8 October (class v3 138.3 MH/s at 268.6 W; class v4 138.0 at 489.3 W on a 500 W host limit)" + } + ], + "src": "~/igneum-fleet/cardbench/rows.jsonl, 8 October" + }, + { + "card": "Intel Arc B580", + "match": [ + "B580" + ], + "vendor": "intel", + "arch": "battlemage", + "memory_gb": 12, + "label": "estimated", + "stock": { + "mhs": 10.6, + "w": 110.0, + "uj": 10.4, + "class": "v4" + }, + "tiers": [ + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 190, + "mhs": 10.6, + "w": 110.0, + "mhw": 0.0964, + "source": "stock", + "label": "estimated", + "uj": 10.4, + "note": "10.6 to 11 MH/s measured on PC 2 (the enclosure), the watts unread; about 110 W by the board's class (OWED); no clock lever in the app for Intel" + } + ], + "src": "docs/bench-log.md, 7 October; site/miner-bench.json" + }, + { + "card": "Intel Arc A750", + "match": [ + "A750" + ], + "vendor": "intel", + "arch": "alchemist", + "memory_gb": 8, + "label": "estimated", + "stock": { + "mhs": 8.0, + "w": 150.0, + "uj": 18.8, + "class": "v4" + }, + "tiers": [ + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 225, + "mhs": 8.0, + "w": 150.0, + "mhw": 0.0533, + "source": "stock", + "label": "estimated", + "uj": 18.8, + "note": "unmeasured; modelled from the B580 and the Alchemist board" + } + ], + "src": "modelled" + }, + { + "card": "Apple M5 Max", + "match": [ + "M5 MAX" + ], + "vendor": "apple", + "arch": "apple-m5", + "memory_gb": 0, + "label": "measured", + "stock": { + "mhs": 26.67, + "w": 37.3, + "uj": 1.4, + "class": "v4" + }, + "tiers": [ + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 0, + "mhs": 26.67, + "w": 37.3, + "mhw": 0.715, + "source": "stock", + "label": "measured", + "uj": 1.4, + "note": "the GPU and DRAM channels of the IOReport meter (not the wall): class v3 27.08 MH/s at 21.0 W (0.78), class v4 26.67 at 37.3 W (1.40), class v5 about 1.43; the package about 17 W more; no lever (no clock cap on Apple silicon)" + } + ], + "src": "docs/analysis/latency-shadow-2026-10-06.md section 3; docs/plans/ember-tune.md (the package figure)" + }, + { + "card": "Apple M4 Max", + "match": [ + "M4 MAX" + ], + "vendor": "apple", + "arch": "apple-m4", + "memory_gb": 0, + "label": "estimated", + "stock": { + "mhs": 26.0, + "w": 40.0, + "uj": 1.55, + "class": "v5" + }, + "tiers": [ + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 0, + "mhs": 26.0, + "w": 40.0, + "mhw": 0.65, + "source": "stock", + "label": "estimated", + "uj": 1.55, + "note": "the same 512-bit LPDDR5X (8,533 against 9,600 MT/s) and a 40-core GPU on N3E: about 26 MH/s; class v3 about 0.85 at the meter, class v5 about 1.55 (the M5 Max's rows scaled); a devnet row from an unnamed Apple laptop read 24.3 MH/s on class v4" + } + ], + "src": "modelled on the M5 Max rows" + }, + { + "card": "Apple M4 Pro", + "match": [ + "M4 PRO" + ], + "vendor": "apple", + "arch": "apple-m4", + "memory_gb": 0, + "label": "estimated", + "stock": { + "mhs": 13.5, + "w": 22.0, + "uj": 1.65, + "class": "v5" + }, + "tiers": [ + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 0, + "mhs": 13.5, + "w": 22.0, + "mhw": 0.6136, + "source": "stock", + "label": "estimated", + "uj": 1.65, + "note": "256-bit LPDDR5X and a 20-core GPU: about 13.5 MH/s; the shadow's per-hash premium does not shrink with the part, so class v5 reads about 1.65 at the meter" + } + ], + "src": "modelled on the M5 Max rows" + }, + { + "card": "Apple M3 Max", + "match": [ + "M3 MAX" + ], + "vendor": "apple", + "arch": "apple-m3", + "memory_gb": 0, + "label": "estimated", + "stock": { + "mhs": 24.0, + "w": 42.0, + "uj": 1.75, + "class": "v5" + }, + "tiers": [ + { + "id": "max", + "clock_mhz": 0, + "power_pct": 100, + "mem_mhz": 0, + "limit_w": 0, + "mhs": 24.0, + "w": 42.0, + "mhw": 0.5714, + "source": "stock", + "label": "estimated", + "uj": 1.75, + "note": "512-bit LPDDR5-6400 and a 40-core GPU on N3B: about 24 MH/s; class v5 about 1.75 at the meter" + } + ], + "src": "modelled on the M5 Max rows" + } + ] +} diff --git a/app/igneum-app/tiers/class-v5-tiers.mjs b/app/igneum-app/tiers/class-v5-tiers.mjs new file mode 100644 index 000000000..1e89fc3ba --- /dev/null +++ b/app/igneum-app/tiers/class-v5-tiers.mjs @@ -0,0 +1,100 @@ +// The class v5 tiers table (app/igneum-app/tiers/class-v5-tiers.json): the loader and the validator the test and the +// app's packaging read it through. The rows are in the shape src/ember.rs tier_from_json reads back from a card's state +// (id, clock_mhz, power_pct, mem_mhz, limit_w, mhs, w), so a card whose own search has not run can be set to a tier +// from this table by /api/tune/tier, and the search replaces the row when it runs. node --test class-v5-tiers.test.mjs +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +export const TIER_IDS = ['efficiency', 'balanced', 'max']; +export const LABELS = ['measured', 'estimated']; +export const SOURCES = ['measured', 'stock']; +export const VENDORS = ['nvidia', 'amd', 'apple', 'intel']; +// the fields tier_from_json reads, every one a number +export const ROW_FIELDS = ['clock_mhz', 'power_pct', 'mem_mhz', 'limit_w', 'mhs', 'w']; +// the brief's card classes (floor lane 4, 8 October 2026): every one must have an entry +export const REQUIRED_CARDS = ['5090', '5080', '5070 Ti', '5070', '5060 Ti', '5060', '4090', '4080', '4070', '3090', '3080', '3070', '3060', + '9070 XT', '7900 XTX', '7800 XT', '7600 XT', '9060 XT', 'H100', 'L40S', 'A100', 'B580', 'A750', 'M5 Max', 'M4 Max', 'M4 Pro', 'M3 Max']; + +export function loadTable(path) { + const p = path || join(dirname(fileURLToPath(import.meta.url)), 'class-v5-tiers.json'); + return JSON.parse(readFileSync(p, 'utf8')); +} + +/** Every fault in the table as a list of strings; an empty list is a valid table. */ +export function validate(t) { + const faults = []; + const f = (s) => faults.push(s); + if (t.class !== 'v5') f(`class is ${t.class}, not v5`); + if (!Array.isArray(t.tier_ids) || t.tier_ids.join() !== TIER_IDS.join()) f('tier_ids must be efficiency, balanced, max'); + for (const k of ['stale_when', 'on_flip', 'measured_flip', 'period']) if (!t.remeasure_rule || typeof t.remeasure_rule[k] !== 'string' || !t.remeasure_rule[k]) f(`remeasure_rule.${k} missing`); + if (!t.v5_over_v4 || typeof t.v5_over_v4.watts_pct !== 'number') f('v5_over_v4.watts_pct missing'); + if (!Array.isArray(t.cards) || t.cards.length === 0) { f('cards missing'); return faults; } + const seen = new Set(); + for (const c of t.cards) { + const name = c.card || '(unnamed)'; + if (seen.has(name)) f(`${name}: listed twice`); + seen.add(name); + if (!VENDORS.includes(c.vendor)) f(`${name}: vendor ${c.vendor}`); + if (!LABELS.includes(c.label)) f(`${name}: label ${c.label}`); + if (!Array.isArray(c.match) || c.match.length === 0) f(`${name}: no match list`); + if (typeof c.src !== 'string' || !c.src) f(`${name}: no src`); + if (!c.stock || typeof c.stock.uj !== 'number' || c.stock.uj <= 0) f(`${name}: stock.uj missing`); + if (!Array.isArray(c.tiers) || c.tiers.length === 0) { f(`${name}: no tiers`); continue; } + const ids = c.tiers.map((r) => r.id); + for (const id of ids) if (!TIER_IDS.includes(id)) f(`${name}: tier id ${id}`); + if (new Set(ids).size !== ids.length) f(`${name}: a tier id repeats`); + const lever = c.vendor === 'nvidia' || c.vendor === 'amd'; + if (lever && ids.join() !== TIER_IDS.join()) f(`${name}: a card with a lever carries all three tiers in order`); + if (!lever && ids.join() !== 'max') f(`${name}: a card with no lever carries the max tier only`); + for (const r of c.tiers) { + for (const k of ROW_FIELDS) if (typeof r[k] !== 'number' || !Number.isFinite(r[k])) f(`${name}/${r.id}: ${k} is not a number`); + if (typeof r.power_pct === 'number' && (r.power_pct < 50 || r.power_pct > 100)) f(`${name}/${r.id}: power_pct ${r.power_pct} outside 50 to 100`); + if (typeof r.clock_mhz === 'number' && (r.clock_mhz < 0 || r.clock_mhz > 4000)) f(`${name}/${r.id}: clock_mhz ${r.clock_mhz}`); + if (!LABELS.includes(r.label)) f(`${name}/${r.id}: label ${r.label}`); + if (!SOURCES.includes(r.source)) f(`${name}/${r.id}: source ${r.source}`); + if (typeof r.uj !== 'number' || r.uj <= 0) f(`${name}/${r.id}: uj missing`); + if (typeof r.note !== 'string' || !r.note) f(`${name}/${r.id}: no note`); + if (r.mhs > 0 && r.w > 0 && r.uj > 0 && Math.abs(r.w / r.mhs - r.uj) / r.uj > 0.025) f(`${name}/${r.id}: uj ${r.uj} is not w over mhs (${(r.w / r.mhs).toFixed(2)})`); + if (r.mhs > 0 && r.w > 0 && typeof r.mhw === 'number' && Math.abs(r.mhs / r.w - r.mhw) / r.mhw > 0.025) f(`${name}/${r.id}: mhw ${r.mhw} is not mhs over w`); + if (r.id === 'max' && r.source !== 'stock') f(`${name}/max: the max tier is the stock row`); + if (r.id === 'max' && (r.clock_mhz !== 0 || r.power_pct !== 100)) f(`${name}/max: stock is unlocked at 100 percent`); + if (c.vendor === 'amd' && (typeof r.core_offset_mhz !== 'number' || typeof r.power_offset_pct !== 'number')) f(`${name}/${r.id}: an AMD row carries core_offset_mhz and power_offset_pct`); + if (c.vendor === 'amd' && r.core_offset_mhz > 0) f(`${name}/${r.id}: an AMD core offset never raises the clock`); + } + const by = Object.fromEntries(c.tiers.map((r) => [r.id, r])); + if (by.efficiency && by.max && by.efficiency.uj > by.max.uj) f(`${name}: efficiency costs more per hash than stock`); + if (by.efficiency && by.balanced && by.balanced.uj + 1e-9 < by.efficiency.uj) f(`${name}: balanced is cheaper per hash than efficiency`); + if (by.balanced && by.max && by.balanced.mhs > 0 && by.max.mhs > 0 && by.balanced.mhs < by.max.mhs * 0.98) f(`${name}: balanced gives up over 2 percent of the top rate`); + if (c.label === 'measured' && !c.tiers.some((r) => r.label === 'measured' && r.source === 'measured') && lever) f(`${name}: a measured card has no measured tuned row`); + } + for (const want of REQUIRED_CARDS) if (!t.cards.some((c) => c.card.includes(want))) f(`no entry for ${want}`); + return faults; +} + +/** The entry a card name matches (the first whose match list hits; the longer match wins, so "5070 Ti" beats "5070"). */ +export function entryFor(t, cardName) { + const n = cardName.toUpperCase(); + let best = null, bestLen = 0; + for (const c of t.cards) for (const m of c.match) if (n.includes(m.toUpperCase()) && m.length > bestLen) { best = c; bestLen = m.length; } + return best; +} + +/** The tier row a card starts from, in the shape tier_from_json reads; null when the table has none. */ +export function tierRow(t, cardName, id) { + const c = entryFor(t, cardName); + if (!c) return null; + return c.tiers.find((r) => r.id === id) || null; +} + +/** The re-measure verdict the app applies on a class flip (src/ember.rs tiers_stale): stale when both classes are known and differ. */ +export function stale(tiersClass, programClass) { + return Boolean(tiersClass) && Boolean(programClass) && tiersClass !== programClass; +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + const t = loadTable(); + const faults = validate(t); + if (faults.length) { console.error(faults.join('\n')); process.exit(1); } + console.log(`class ${t.class}: ${t.cards.length} card classes, ${t.cards.filter((c) => c.label === 'measured').length} measured`); +} diff --git a/app/igneum-app/tiers/class-v5-tiers.test.mjs b/app/igneum-app/tiers/class-v5-tiers.test.mjs new file mode 100644 index 000000000..087379df8 --- /dev/null +++ b/app/igneum-app/tiers/class-v5-tiers.test.mjs @@ -0,0 +1,109 @@ +// node --test app/igneum-app/tiers/class-v5-tiers.test.mjs +// The class v5 tiers table: the shipped file validates; the measured rows carry the record's numbers; the match rule +// picks the longer name; a class flip reads stale; and the known-failed cases (a bad power rung, a missing field, a +// tuned row dearer than stock, a card class left out) are refused, so a stale or broken table cannot pass. +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { loadTable, validate, entryFor, tierRow, stale, REQUIRED_CARDS, ROW_FIELDS } from './class-v5-tiers.mjs'; + +const clone = (t) => JSON.parse(JSON.stringify(t)); + +test('the shipped table validates with no faults', () => { + const t = loadTable(); + assert.deepEqual(validate(t), []); + assert.equal(t.class, 'v5'); + assert.ok(t.cards.length >= REQUIRED_CARDS.length); +}); + +test('the measured rows are the record\'s (the 5090 at its 1,300 MHz knee, the 5080 at 1,100, the 4070 at its tune, the 9070 XT grid, the M5 Max meter)', () => { + const t = loadTable(); + const r5090 = tierRow(t, 'NVIDIA GeForce RTX 5090', 'balanced'); + assert.equal(r5090.clock_mhz, 1300); + assert.equal(r5090.label, 'measured'); + assert.ok(Math.abs(r5090.uj - 2.38) < 0.01, `the 5090 balanced row reads 2.38 microjoules under class v5: ${r5090.uj}`); + const e5090 = tierRow(t, 'NVIDIA GeForce RTX 5090', 'efficiency'); + assert.equal(e5090.clock_mhz, 1200); + const r5080 = tierRow(t, 'NVIDIA GeForce RTX 5080', 'efficiency'); + assert.equal(r5080.clock_mhz, 1100); + assert.ok(Math.abs(r5080.uj - 2.06) < 0.01); + const r4070 = tierRow(t, 'NVIDIA GeForce RTX 4070', 'efficiency'); + assert.equal(r4070.clock_mhz, 1860); + assert.equal(r4070.power_pct, 50); + const amd = tierRow(t, 'AMD Radeon RX 9070 XT', 'efficiency'); + assert.equal(amd.core_offset_mhz, -500); + assert.equal(amd.power_offset_pct, -30); + assert.ok(Math.abs(amd.w - 149.3) < 0.01); + const apple = entryFor(t, 'Apple M5 Max'); + assert.equal(apple.tiers.length, 1); + assert.equal(apple.tiers[0].id, 'max'); + assert.ok(Math.abs(apple.tiers[0].uj - 1.40) < 0.01); +}); + +test('every entry carries the three tiers where the card has a lever, and only max where it has none', () => { + const t = loadTable(); + for (const c of t.cards) { + const ids = c.tiers.map((r) => r.id).join(); + if (c.vendor === 'nvidia' || c.vendor === 'amd') assert.equal(ids, 'efficiency,balanced,max', c.card); + else assert.equal(ids, 'max', c.card); + for (const r of c.tiers) for (const k of ROW_FIELDS) assert.equal(typeof r[k], 'number', `${c.card}/${r.id}.${k}`); + } +}); + +test('the match rule takes the longer name: a 5070 Ti is not a 5070, a 4060 Ti is not a 4060, a 9060 XT is not a 9070 XT', () => { + const t = loadTable(); + assert.equal(entryFor(t, 'NVIDIA GeForce RTX 5070 Ti').card, 'NVIDIA GeForce RTX 5070 Ti'); + assert.equal(entryFor(t, 'NVIDIA GeForce RTX 5070').card, 'NVIDIA GeForce RTX 5070'); + assert.equal(entryFor(t, 'NVIDIA GeForce RTX 4060 Ti').card, 'NVIDIA GeForce RTX 4060 Ti'); + assert.equal(entryFor(t, 'NVIDIA GeForce RTX 4060').card, 'NVIDIA GeForce RTX 4060'); + assert.equal(entryFor(t, 'AMD Radeon RX 9060 XT').card, 'AMD Radeon RX 9060 XT'); + assert.equal(entryFor(t, 'amd:gfx1201').card, 'AMD Radeon RX 9070 XT'); + assert.equal(entryFor(t, 'NVIDIA GeForce GTX 1080 Ti'), null); + assert.equal(tierRow(t, 'NVIDIA GeForce GTX 1080 Ti', 'max'), null); +}); + +test('a class flip reads stale exactly as src/ember.rs tiers_stale does', () => { + assert.equal(stale('v4', 'v5'), true); + assert.equal(stale('v5', 'v5'), false); + assert.equal(stale('', 'v5'), false); + assert.equal(stale('v4', ''), false); + const t = loadTable(); + assert.ok(t.remeasure_rule.measured_flip.includes('0.0 percent of rate')); + assert.equal(t.v5_over_v4.watts_pct, 2.0); +}); + +test('known-failed: a power rung under 50 is refused', () => { + const t = clone(loadTable()); + t.cards[0].tiers[0].power_pct = 40; + assert.ok(validate(t).some((s) => s.includes('power_pct 40'))); +}); + +test('known-failed: a row missing a field tier_from_json reads is refused', () => { + const t = clone(loadTable()); + delete t.cards[1].tiers[1].limit_w; + assert.ok(validate(t).some((s) => s.includes('limit_w is not a number'))); +}); + +test('known-failed: a tuned row dearer per hash than stock is refused, and a max tier that is not stock', () => { + const t = clone(loadTable()); + const c = t.cards.find((x) => x.card.includes('4080')); + c.tiers[0].uj = c.tiers[2].uj + 1; c.tiers[0].w = c.tiers[0].uj * c.tiers[0].mhs; c.tiers[0].mhw = c.tiers[0].mhs / c.tiers[0].w; + assert.ok(validate(t).some((s) => s.includes('efficiency costs more per hash than stock'))); + const u = clone(loadTable()); + u.cards[0].tiers[2].clock_mhz = 1500; + assert.ok(validate(u).some((s) => s.includes('stock is unlocked at 100 percent'))); +}); + +test('known-failed: a card class of the brief left out is refused, and a stale uj (not w over mhs) is refused', () => { + const t = clone(loadTable()); + t.cards = t.cards.filter((c) => !c.card.includes('3060')); + assert.ok(validate(t).some((s) => s === 'no entry for 3060')); + const u = clone(loadTable()); + u.cards[0].tiers[1].uj = 9.99; + assert.ok(validate(u).some((s) => s.includes('is not w over mhs'))); +}); + +test('known-failed: a table under another class is refused', () => { + const t = clone(loadTable()); + t.class = 'v4'; + assert.ok(validate(t).some((s) => s.includes('not v5'))); +});