Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ docker run --rm bitaxe-benchmark 192.168.2.26 --set-values -v 1150 -f 780

## **Configuration**

The script includes several configurable parameters. These can be adjusted in the bitaxe_hashrate_benchmark.py file:
The script includes several configurable parameters. These can be adjusted in the UI or in the bitaxe_hashrate_benchmark.py file:

* Max Acceptable Error Percentage: 1%.
* Maximum chip temperature: 66°C
* Maximum VR temperature: 86°C
* Maximum allowed voltage: 1400mV
Expand All @@ -136,15 +137,16 @@ The benchmark results are saved to `bitaxe_benchmark_results_<ip_address>_<times
* Top 5 performing configurations ranked by hashrate
* Top 5 most efficient configurations ranked by J/TH
* For each configuration:
* Average hashrate (with outlier removal)
* Temperature readings (excluding initial warmup period)
* VR temperature readings (when available)
* Power efficiency metrics (J/TH)
* Average Power (Watts)
* Average Fan Speed (Percentage or RPM, if available from API)
* Input voltage measurements
* Voltage/frequency combinations tested
* Error reason (if any) for a specific iteration
* Average hashrate (with outlier removal)
* Temperature readings (excluding initial warmup period)
* VR temperature readings (when available)
* Power efficiency metrics (J/TH)
* Average Power (Watts)
* Average Fan Speed (Percentage or RPM, if available from API)
* Input voltage measurements
* Voltage/frequency combinations tested
* Error percentage (live and per test)
* Error reason (if any) for a specific iteration

## **Safety Features**

Expand All @@ -167,6 +169,7 @@ The tool follows this process:
1. Starts with user-specified or default voltage/frequency
2. Tests each combination for 10 minutes
3. Validates hashrate is within 6% of theoretical maximum
4. Monitors error percentage live and flags tests as failed if error % exceeds the configured threshold
4. Incrementally adjusts settings:
* Increases frequency if stable
* Increases voltage if unstable
Expand All @@ -183,6 +186,7 @@ The tool implements several data processing techniques to ensure accurate result
* Removes 3 highest and 3 lowest hashrate readings to eliminate outliers
* Excludes first 6 temperature readings during warmup period
* Validates hashrate is within 6% of theoretical maximum
* Monitors error percentage live and flags tests as failed if error % exceeds the configured threshold
* Averages power consumption across entire test period
* Monitors VR temperature when available
* Calculates efficiency in Joules per Terahash (J/TH)
Expand Down
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
<title>Bitaxe Hashrate Benchmark</title>
</head>
<body>
<div id="root"></div>
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/components/ConfigEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,30 @@ export function ConfigEditor({ config, onSave, disabled }: ConfigEditorProps) {
</div>
</div>

{/* Analysis */}
<div>
<h3 className="text-sm font-medium text-gray-300 mb-2">Analysis</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs text-gray-400">Max Acceptable Error %</label>
<input
type="number"
step="0.01"
min="0"
value={
editedConfig.analysis.max_error_percent === undefined
? 1.0
: editedConfig.analysis.max_error_percent
}
onChange={e => handleChange('analysis', 'max_error_percent', e.target.value)}
disabled={disabled}
className="mt-1 w-full bg-gray-700 border border-gray-600 rounded px-2 py-1 text-white text-sm disabled:opacity-50"
/>
<span className="text-xs text-gray-500">Default: 1%</span>
</div>
</div>
</div>

{error && (
<div className="text-red-400 text-sm">{error}</div>
)}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/ProgressDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export function ProgressDisplay({ progress, voltageStep }: ProgressDisplayProps)

const { sample, core_voltage, frequency, sample_number, total_samples, progress_percent, running_stddev } = progress;

// Try to get error percent from sample if present (for live display)
// If not present, display "--"
// If you have a better source for live error %, replace here
const errorPercent = (progress as any).error_percent;

return (
<div className="bg-gray-800 rounded-lg p-4">
<h2 className="text-lg font-semibold text-white mb-4">Live Progress</h2>
Expand Down Expand Up @@ -119,6 +124,13 @@ export function ProgressDisplay({ progress, voltageStep }: ProgressDisplayProps)
color="text-gray-300"
/>
)}
{/* Error % in lower right */}
<MetricCard
label="Error %"
value={typeof errorPercent === 'number' && !isNaN(errorPercent) ? errorPercent.toFixed(2) : '--'}
unit="%"
color="text-pink-400"
/>
</div>
</div>
);
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export function ResultsTable({
<th className="pb-2 pr-4">Temp</th>
<th className="pb-2 pr-4">Power</th>
<th className="pb-2 pr-4">Efficiency</th>
<th className="pb-2 pr-4">Error %</th>
<th className="pb-2">Status</th>
</tr>
</thead>
Expand Down Expand Up @@ -185,6 +186,11 @@ export function ResultsTable({
</span>
<span className="text-gray-400 ml-1">J/TH</span>
</td>
<td className="py-2 pr-4 font-mono text-white">
{typeof result.error_percent === 'number' && !isNaN(result.error_percent)
? result.error_percent.toFixed(2)
: '--'}
</td>
<td className="py-2">
{result.error_reason ? (
<span className="text-red-400 text-xs">
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface SampleProgress {
frequency: number;
sample: SampleData;
running_stddev: number;
error_percent?: number | null;
timestamp: string;
}

Expand All @@ -60,6 +61,7 @@ export interface IterationResult {
efficiency_jth: number;
hashrate_within_tolerance: boolean;
error_reason: string | null;
error_percent?: number | null;
}

export interface IterationComplete {
Expand Down Expand Up @@ -145,6 +147,7 @@ export interface AnalysisConfig {
trim_outliers: number;
warmup_samples: number;
min_samples: number;
max_error_percent?: number; // Maximum acceptable error percentage (default 1%)
}

export interface BenchmarkConfig {
Expand Down
33 changes: 33 additions & 0 deletions src/benchmark/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,28 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="SYSTEM_INFO_FAILURE",
error_percent=None,
)

# --- Error Percentage Check ---
error_percentage = info.get("errorPercentage", 100.0) # Default to 100% if not provided
max_error = getattr(self.config.analysis, "max_error_percent", 1.0)
if error_percentage is not None and error_percentage > max_error:
self._log(
"error",
f"Error percentage {error_percentage:.2f}% exceeds threshold ({max_error:.2f}%)"
)
return IterationResult(
core_voltage=voltage,
frequency=frequency,
average_hashrate=0,
hashrate_stddev=0,
average_temperature=info.get("temp", 0),
average_power=info.get("power", 0),
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="ERROR_PERCENTAGE_EXCEEDED",
error_percent=error_percentage,
)

temp = info.get("temp")
Expand All @@ -303,6 +325,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="TEMPERATURE_DATA_FAILURE" if temp is None else "TEMPERATURE_BELOW_5",
error_percent=error_percentage,
)

if temp >= self.config.safety.max_temp:
Expand All @@ -316,6 +339,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="CHIP_TEMP_EXCEEDED",
error_percent=error_percentage,
)

if vr_temp is not None and vr_temp >= self.config.safety.max_vr_temp:
Expand All @@ -330,6 +354,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="VR_TEMP_EXCEEDED",
error_percent=error_percentage,
)

if input_voltage < self.config.safety.min_input_voltage:
Expand All @@ -343,6 +368,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="INPUT_VOLTAGE_BELOW_MIN",
error_percent=error_percentage,
)

if input_voltage > self.config.safety.max_input_voltage:
Expand All @@ -356,6 +382,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="INPUT_VOLTAGE_ABOVE_MAX",
error_percent=error_percentage,
)

if hash_rate is None or power is None:
Expand All @@ -369,6 +396,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="HASHRATE_POWER_DATA_FAILURE",
error_percent=error_percentage,
)

if power > self.config.safety.max_power:
Expand All @@ -382,6 +410,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="POWER_CONSUMPTION_EXCEEDED",
error_percent=error_percentage,
)

# Record sample
Expand Down Expand Up @@ -413,6 +442,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
fan_speed=fan_speed,
),
running_stddev=self._running_stddev(sample_num + 1, s1, s2),
error_percent=error_percentage,
)
)

Expand All @@ -432,6 +462,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="NO_DATA_COLLECTED",
error_percent=error_percentage,
)

# Trim outliers
Expand Down Expand Up @@ -469,6 +500,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=float("inf"),
hashrate_within_tolerance=False,
error_reason="ZERO_HASHRATE",
error_percent=error_percentage,
)

hashrate_within_tolerance = average_hashrate >= expected_hashrate * self.config.analysis.hashrate_tolerance
Expand All @@ -485,6 +517,7 @@ async def _run_iteration(self, voltage: int, frequency: int, iteration_num: int)
efficiency_jth=efficiency_jth,
hashrate_within_tolerance=hashrate_within_tolerance,
error_reason=None,
error_percent=error_percentage,
)

async def _check_pause(self) -> bool:
Expand Down
1 change: 1 addition & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class AnalysisConfig(BaseModel):
trim_outliers: int = Field(default=3, description="Number of outliers to trim from each end")
warmup_samples: int = Field(default=6, description="Number of warmup samples to exclude from temp averaging")
min_samples: int = Field(default=7, description="Minimum samples required for valid benchmark")
max_error_percent: float = Field(default=1.0, description="Maximum acceptable error percentage before marking iteration as bad")


class BenchmarkConfig(BaseModel):
Expand Down
2 changes: 2 additions & 0 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class SampleProgress(BaseModel):
frequency: int = Field(description="Current frequency in MHz")
sample: SampleData = Field(description="Sample measurement data")
running_stddev: float = Field(description="Running standard deviation of hashrate")
error_percent: float = Field(default=None, description="Current error percentage reported by device (if available)")
timestamp: datetime = Field(default_factory=datetime.now)


Expand All @@ -77,6 +78,7 @@ class IterationResult(BaseModel):
efficiency_jth: float = Field(description="Efficiency in J/TH")
hashrate_within_tolerance: bool = Field(description="Whether hashrate met expected threshold")
error_reason: Optional[str] = Field(default=None, description="Error if iteration failed")
error_percent: Optional[float] = Field(default=None, description="Error percentage reported by device (if available)")


class IterationComplete(BaseModel):
Expand Down