HPL 2.3 from netlib compiled against OpenBLAS with a minimal single-process MPI stub — no MPI package required in the ISO. Matrix size is auto-sized to 80% of total RAM at runtime. Build: - VERSIONS: HPL_VERSION=2.3, HPL_SHA256=32c5c17d… - build-hpl.sh: downloads HPL + OpenBLAS from Debian 12 repo, compiles xhpl with a self-contained mpi_stub.c - build.sh: step 80-hpl, injects xhpl + libopenblas into overlay Runtime: - bee-hpl: generates HPL.dat (N auto from /proc/meminfo, NB=256, P=1 Q=1), runs xhpl, prints standard WR... Gflops output - platform/hpl.go: RunHPL(), parses WR line → GFlops + PASSED/FAILED - tasks.go: target "hpl" - pages.go: LINPACK (HPL) card in validate/stress grid (stress-only) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
143 lines
4.2 KiB
Go
143 lines
4.2 KiB
Go
package platform
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// HPLOptions configures the HPL (LINPACK) benchmark run.
|
|
type HPLOptions struct {
|
|
MemFraction float64 // fraction of RAM to use (default 0.80)
|
|
NB int // block size (default 256)
|
|
}
|
|
|
|
// HPLResult holds the parsed result of an HPL run.
|
|
type HPLResult struct {
|
|
N int // matrix dimension
|
|
NB int // block size
|
|
P int // process grid rows
|
|
Q int // process grid cols
|
|
TimeSec float64 // wall time in seconds
|
|
GFlops float64 // achieved performance
|
|
Residual float64 // backward error residual (from HPL verification line)
|
|
Status string // "PASSED" or "FAILED"
|
|
RawOutput string // full xhpl output
|
|
}
|
|
|
|
func applyHPLDefaults(opts *HPLOptions) {
|
|
if opts.MemFraction <= 0 || opts.MemFraction > 1 {
|
|
opts.MemFraction = 0.80
|
|
}
|
|
if opts.NB <= 0 {
|
|
opts.NB = 256
|
|
}
|
|
}
|
|
|
|
// RunHPL runs bee-hpl and returns parsed results plus a tar.gz artifact path.
|
|
func (s *System) RunHPL(ctx context.Context, baseDir string, opts HPLOptions, logFunc func(string)) (string, *HPLResult, error) {
|
|
applyHPLDefaults(&opts)
|
|
|
|
if baseDir == "" {
|
|
baseDir = "/var/log/bee-sat"
|
|
}
|
|
ts := time.Now().UTC().Format("20060102-150405")
|
|
runDir := filepath.Join(baseDir, "hpl-"+ts)
|
|
if err := os.MkdirAll(runDir, 0755); err != nil {
|
|
return "", nil, fmt.Errorf("mkdir %s: %w", runDir, err)
|
|
}
|
|
|
|
logPath := filepath.Join(runDir, "hpl.log")
|
|
|
|
cmd := []string{
|
|
"bee-hpl",
|
|
"--mem-fraction", strconv.FormatFloat(opts.MemFraction, 'f', 2, 64),
|
|
"--nb", strconv.Itoa(opts.NB),
|
|
}
|
|
|
|
if logFunc != nil {
|
|
logFunc(fmt.Sprintf("HPL: N will be auto-sized to %.0f%% of RAM, NB=%d", opts.MemFraction*100, opts.NB))
|
|
}
|
|
|
|
out, err := runSATCommandCtx(ctx, "", "hpl", cmd, nil, logFunc)
|
|
_ = os.WriteFile(logPath, out, 0644)
|
|
|
|
result := parseHPLOutput(string(out))
|
|
result.RawOutput = string(out)
|
|
|
|
if err != nil && err != context.Canceled {
|
|
return "", result, fmt.Errorf("bee-hpl failed: %w", err)
|
|
}
|
|
if err == nil && result.GFlops <= 0 {
|
|
return "", result, fmt.Errorf("HPL completed but no Gflops result found in output")
|
|
}
|
|
|
|
// Write summary
|
|
summary := fmt.Sprintf("N=%d NB=%d time=%.2fs gflops=%.3f status=%s\n",
|
|
result.N, result.NB, result.TimeSec, result.GFlops, result.Status)
|
|
_ = os.WriteFile(filepath.Join(runDir, "summary.txt"), []byte(summary), 0644)
|
|
|
|
if logFunc != nil {
|
|
logFunc(fmt.Sprintf("HPL result: N=%d NB=%d %.2fs %.3f Gflops %s",
|
|
result.N, result.NB, result.TimeSec, result.GFlops, result.Status))
|
|
}
|
|
|
|
ts2 := time.Now().UTC().Format("20060102-150405")
|
|
archive := filepath.Join(baseDir, "hpl-"+ts2+".tar.gz")
|
|
if archErr := createTarGz(archive, runDir); archErr != nil {
|
|
return runDir, result, err
|
|
}
|
|
return archive, result, err
|
|
}
|
|
|
|
// parseHPLOutput extracts N, NB, time, and Gflops from standard HPL output.
|
|
//
|
|
// HPL prints a result line of the form:
|
|
//
|
|
// WR00L2L2 45312 256 1 1 1234.56 5.678e+01
|
|
// T/V N NB P Q Time Gflops
|
|
func parseHPLOutput(output string) *HPLResult {
|
|
result := &HPLResult{Status: "FAILED"}
|
|
for _, line := range strings.Split(output, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
// Result line starts with WR
|
|
if strings.HasPrefix(line, "WR") {
|
|
fields := strings.Fields(line)
|
|
// WR00L2L2 N NB P Q Time Gflops
|
|
if len(fields) >= 7 {
|
|
result.N, _ = strconv.Atoi(fields[1])
|
|
result.NB, _ = strconv.Atoi(fields[2])
|
|
result.P, _ = strconv.Atoi(fields[3])
|
|
result.Q, _ = strconv.Atoi(fields[4])
|
|
result.TimeSec, _ = strconv.ParseFloat(fields[5], 64)
|
|
result.GFlops, _ = strconv.ParseFloat(fields[6], 64)
|
|
}
|
|
}
|
|
// Verification line: "||Ax-b||_oo/(eps*(||A||_oo*||x||_oo+||b||_oo)*N)= ... PASSED"
|
|
if strings.Contains(line, "PASSED") {
|
|
result.Status = "PASSED"
|
|
fields := strings.Fields(line)
|
|
for i, f := range fields {
|
|
if f == "PASSED" && i > 0 {
|
|
result.Residual, _ = strconv.ParseFloat(fields[i-1], 64)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// hplAvailable returns true if bee-hpl and xhpl are present and executable.
|
|
func hplAvailable() bool {
|
|
if _, err := exec.LookPath("bee-hpl"); err != nil {
|
|
return false
|
|
}
|
|
_, err := os.Stat("/usr/local/lib/bee/xhpl")
|
|
return err == nil
|
|
}
|