Skip to content

Commit

Permalink
Merge pull request #398 from tphakala/enhance-cpu-detect
Browse files Browse the repository at this point in the history
feat: add CPU specification handling for optimized thread management
  • Loading branch information
tphakala authored Jan 19, 2025
2 parents 3c420a4 + 5f43cbc commit 5a46254
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 2 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/k3a/html2text v1.2.1
github.com/klauspost/cpuid/v2 v2.2.9
github.com/labstack/echo/v4 v4.13.3
github.com/prometheus/client_golang v1.20.3
github.com/prometheus/client_model v0.6.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ github.com/k3a/html2text v1.2.1 h1:nvnKgBvBR/myqrwfLuiqecUtaK1lB9hGziIJKatNFVY=
github.com/k3a/html2text v1.2.1/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down
35 changes: 33 additions & 2 deletions internal/birdnet/birdnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"sync"

"github.com/tphakala/birdnet-go/internal/conf"
"github.com/tphakala/birdnet-go/internal/cpuspec"
"github.com/tphakala/go-tflite"
"github.com/tphakala/go-tflite/delegates/xnnpack"
)
Expand Down Expand Up @@ -129,7 +130,22 @@ func (bn *BirdNET) initializeModel() error {
modelVersion = bn.Settings.BirdNET.ModelPath
}

fmt.Printf("%s model initialized, using %v threads of available %v CPUs\n", modelVersion, threads, runtime.NumCPU())
// Get CPU information for detailed message
var initMessage string
if bn.Settings.BirdNET.Threads == 0 {
spec := cpuspec.GetCPUSpec()
if spec.PerformanceCores > 0 {
initMessage = fmt.Sprintf("%s model initialized, optimized to use %v threads on %v P-cores (system has %v total CPUs)",
modelVersion, threads, spec.PerformanceCores, runtime.NumCPU())
} else {
initMessage = fmt.Sprintf("%s model initialized, using %v threads of available %v CPUs",
modelVersion, threads, runtime.NumCPU())
}
} else {
initMessage = fmt.Sprintf("%s model initialized, using configured %v threads of available %v CPUs",
modelVersion, threads, runtime.NumCPU())
}
fmt.Println(initMessage)
return nil
}

Expand Down Expand Up @@ -173,9 +189,24 @@ func (bn *BirdNET) initializeMetaModel() error {
// determineThreadCount calculates the appropriate number of threads to use based on settings and system capabilities.
func (bn *BirdNET) determineThreadCount(configuredThreads int) int {
systemCpuCount := runtime.NumCPU()
if configuredThreads <= 0 || configuredThreads > systemCpuCount {

// If threads are configured to 0, try to get optimal count from cpuspec
if configuredThreads == 0 {
spec := cpuspec.GetCPUSpec()
optimalThreads := spec.GetOptimalThreadCount()
if optimalThreads > 0 {
return min(optimalThreads, systemCpuCount)
}

// If cpuspec doesn't know the CPU, use all available cores
return systemCpuCount
}

// If threads are configured but exceed system CPU count, limit to system CPU count
if configuredThreads > systemCpuCount {
return systemCpuCount
}

return configuredThreads
}

Expand Down
170 changes: 170 additions & 0 deletions internal/cpuspec/cpuspec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package cpuspec

import (
"regexp"
"runtime"
"strings"

"github.com/klauspost/cpuid/v2"
)

// CPUSpec contains information about CPU specifications
type CPUSpec struct {
BrandName string
PerformanceCores int
EfficiencyCores int
}

// GetCPUSpec returns CPU specifications including the number of performance cores
func GetCPUSpec() CPUSpec {
brandName := cpuid.CPU.BrandName

spec := CPUSpec{
BrandName: brandName,
PerformanceCores: determinePerformanceCores(brandName),
}

return spec
}

// GetOptimalThreadCount returns the recommended number of threads for BirdNet analysis
func (c CPUSpec) GetOptimalThreadCount() int {
// Get actual available CPU count (important for VMs)
availableCPUs := runtime.NumCPU()

// For hybrid architectures (with P and E cores), we primarily want to use Performance cores
if c.PerformanceCores > 0 {
recommendedThreads := c.PerformanceCores
if recommendedThreads > availableCPUs {
return availableCPUs
}
return recommendedThreads
}

// Fallback to using all logical cores if we can't determine P-cores
return cpuid.CPU.LogicalCores
}

func determinePerformanceCores(brandName string) int {
brandName = strings.ToLower(brandName)

// Intel 12th, 13th, 14th gen and Core Ultra mapping
intelCoreRegex := regexp.MustCompile(`intel.*(?:core.*i[357,9]-(\d{5})|core.*ultra\s+([579])\s+(?:processor\s+)?(\d{3}))`)
if matches := intelCoreRegex.FindStringSubmatch(brandName); len(matches) > 1 {
if matches[1] != "" { // Legacy Core i series
model := matches[1]
switch {
case strings.HasPrefix(model, "127"): // 12th gen
switch model {
case "12900":
return 8 // 12900K, 12900KS, 12900KF have 8 P-cores
case "12700":
return 8 // 12700K, 12700KF, 12700 have 8 P-cores
case "12600":
return 6 // 12600K, 12600KF have 6 P-cores
case "12400":
return 6 // 12400, 12400F have 6 P-cores
case "12100":
return 4 // 12100, 12100F have 4 P-cores
}
case strings.HasPrefix(model, "137"): // 13th gen
switch model {
case "13900":
return 8 // 13900K, 13900KS, 13900KF, 13900 all have 8 P-cores
case "13700":
return 8 // 13700K, 13700KF, 13700 all have 8 P-cores
case "13600":
return 6 // 13600K, 13600KF have 6 P-cores
case "13500":
return 6 // 13500 has 6 P-cores
case "13400":
return 6 // 13400, 13400F have 6 P-cores
case "13100":
return 4 // 13100, 13100F have 4 P-cores
}
case strings.HasPrefix(model, "147"): // 14th gen
switch model {
case "14900":
return 8 // 14900K, 14900KF have 8 P-cores
case "14700":
return 8 // 14700K, 14700KF have 8 P-cores
case "14600":
return 6 // 14600K, 14600KF have 6 P-cores
case "14400":
return 6 // 14400, 14400F have 6 P-cores
case "14100":
return 4 // 14100, 14100F have 4 P-cores
}
}
} else if matches[2] != "" { // Core Ultra series
series := matches[2]
model := matches[3]
switch {
case series == "9":
switch model {
case "285":
return 8 // Core Ultra 9 285(K): 8 P-cores (not 16 - that was E-cores)
}
case series == "7":
switch model {
case "265", "265K", "265H":
return 8 // Core Ultra 7 265: 8 P-cores (not 12 - that was E-cores)
case "255":
return 8 // Core Ultra 7 255: 8 P-cores
}
case series == "5":
switch model {
case "235":
return 6 // Core Ultra 5 235: 6 P-cores
case "225":
return 4 // Core Ultra 5 225: 4 P-cores
}
}
}
}

// Apple Silicon mapping
appleRegex := regexp.MustCompile(`(?i)apple\s+(m[123,4]\s*(pro|max|ultra)?)\s*`)
if matches := appleRegex.FindStringSubmatch(brandName); len(matches) > 1 {
chip := strings.ToLower(strings.TrimSpace(matches[1]))
switch chip {
// M1 series
case "m1":
return 4 // Base M1: 4 performance cores
case "m1 pro":
return 8 // M1 Pro: 8 or 6 performance cores (we'll use higher value)
case "m1 max":
return 8 // M1 Max: 8 performance cores
case "m1 ultra":
return 16 // M1 Ultra: 16 performance cores (2x Max)
// M2 series
case "m2":
return 4 // Base M2: 4 performance cores
case "m2 pro":
return 8 // M2 Pro: 8 or 6 performance cores (we'll use higher value)
case "m2 max":
return 12 // M2 Max: 12 performance cores
case "m2 ultra":
return 24 // M2 Ultra: 24 performance cores (2x Max)
// M3 series
case "m3":
return 4 // Base M3: 4 performance cores
case "m3 pro":
return 8 // M3 Pro: 8 or 6 performance cores (we'll use higher value)
case "m3 max":
return 12 // M3 Max: 12 performance cores
case "m3 ultra":
return 24 // M3 Ultra: 24 performance cores (2x Max)
// M4 series
case "m4":
return 6 // Base M4: 6 performance cores
case "m4 pro":
return 8 // M4 Pro: 8 performance cores
case "m4 max":
return 12 // M4 Max: 12 performance cores
}
}

// If we can't determine P-cores, return 0
return 0
}

0 comments on commit 5a46254

Please sign in to comment.