-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.toml
More file actions
196 lines (179 loc) · 6.74 KB
/
Copy pathconfig.toml
File metadata and controls
196 lines (179 loc) · 6.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# unifand configuration — annotated default
#
# Temperatures are °C, duties are percent (0-100).
# The control temperature each tick is max(gpu_temp, cpu_temp - cpu_offset);
# if the GPU sensor is unavailable, raw CPU temp is used (and the
# unifand_degraded metric reads 1).
# Seconds between control-loop ticks.
poll_interval_secs = 5
# Hub fan channels to drive (1-4). Only list channels with fans attached.
channels = [1, 2]
# Duty applied on shutdown (SIGTERM) and when every sensor fails.
# Pick something you could live with indefinitely under load.
fallback_duty = 60
# Subtracted from CPU temp before comparing against GPU temp, so a
# CPU-only spike doesn't over-drive fans tuned to GPU thresholds.
cpu_offset = 10.0
# Duty only changes when the control temp moved at least this far...
hysteresis_c = 2.0
# ...or the target duty differs from the current duty by at least this much.
min_duty_delta = 5
# Duty moves at most this many points per tick (ramp smoothing).
max_step_per_tick = 10
# The fan curve: piecewise-linear, clamped at both ends.
# Temps must be strictly increasing.
[[curve]]
temp = 35.0
duty = 30
[[curve]]
temp = 55.0
duty = 40
[[curve]]
temp = 68.0
duty = 65
[[curve]]
temp = 75.0
duty = 85
[[curve]]
temp = 80.0
duty = 100
# Optional: fan LEDs display a color mapped to the current duty.
[rgb]
enabled = true
# This declares the LED chain length to the hub (start-packet byte 3);
# it is NOT just cosmetic — a value shorter than the physically-connected
# chain leaves the tail fans dark (e.g. 4 on a 6-fan chain lights only
# fans 1-4). 6 is the hub's max chain length and safe to leave as-is even
# on shorter chains. Only lower it, or use [rgb.fans] below, if you have a
# specific reason to.
fans_per_channel = 6
# Optional per-channel override when channels have different physical
# chain lengths (e.g. a 3-fan chain on channel 1, 6-fan on channel 2).
# Keys are channel numbers (as strings, since TOML table keys are
# strings), values are 1..=6. Any channel not listed here falls back to
# fans_per_channel above.
# [rgb.fans]
# 1 = 3
# 2 = 6
# 0 = 100% brightness ... 8 = off (hub's own scale).
brightness = 0
# Duty range is quantized into this many bins; colors are only written
# when the bin changes (keeps LED traffic minimal).
buckets = 8
# Color gradient over duty. Colors are [r, g, b].
[[rgb.stops]]
duty = 30
color = [0, 0, 255] # blue — idle
[[rgb.stops]]
duty = 50
color = [0, 255, 0] # green
[[rgb.stops]]
duty = 75
color = [255, 255, 0] # yellow
[[rgb.stops]]
duty = 90
color = [255, 128, 0] # orange
[[rgb.stops]]
duty = 100
color = [255, 0, 0] # red — full tilt
[metrics]
# Prometheus text endpoint.
listen = "0.0.0.0:9877"
# Alarm ladder: LEDs escalate from the static gradient to animated alerts.
# States: sustained-hot (slow breathing in the gradient color), near-limit
# (red breathing that speeds up the longer you sit there), fault (orange/red
# runway when all sensors are lost). See README "The alarm ladder".
[rgb.alerts]
enabled = true
# Sustained-hot: control temp at/above this...
sustained_hot_c = 75.0
# ...continuously for this long.
sustained_after_secs = 120
# Near-limit: within this many degrees of the curve's top temp, or duty at 100%.
near_limit_margin_c = 3.0
# Red pulse speeds up one hub step per interval while near-limit persists.
escalate_every_secs = 30
# A state must be clear this long before the ladder steps back down.
cooldown_secs = 60
alert_color = [255, 0, 0]
fault_colors = [[255, 80, 0], [255, 0, 0]]
# Multi-signal fusion: fans follow the highest duty any enabled signal's
# curve commands (raise-only — a signal can raise duty above what the die
# temp asks for, never lower it). Off by default; with this section absent
# or enabled = false, behavior is exactly the legacy single-curve loop
# above. See README "Multi-signal fusion" and, for a complete tuned
# starting point, examples/config-multisignal.toml.
#
# Note: with fusion enabled, hysteresis_c above is inert (fusion decides in
# duty space, so change suppression degenerates to min_duty_delta).
[signals]
enabled = false
# GPU die temperature as a fusion signal. With curve omitted it uses the
# top-level [[curve]] above, so enabling [signals] alone changes nothing
# about how die temp maps to duty. alpha is EWMA smoothing per tick
# (1.0 = no smoothing).
[signals.gpu_temp]
enabled = true
alpha = 1.0
# Own curve (optional; falls back to the top-level [[curve]] when omitted):
# [[signals.gpu_temp.curve]]
# temp = 35.0
# duty = 30
# CPU temperature as a fusion signal. offset_c plays the same role as the
# top-level cpu_offset (subtracted before the curve lookup); with curve
# omitted it uses the top-level [[curve]].
[signals.cpu_temp]
enabled = true
offset_c = 10.0
alpha = 1.0
# GPU power draw. Disabled by default and has no top-level fallback — a
# curve is required when enabled. unit selects the curve's X axis:
# "watts" (the default — absolute, tuned to one card) or "percent_tdp"
# (percent of the enforced power limit — portable across cards).
# Power rises fast and falls slow through an asymmetric filter: rise_alpha
# tracks load onset quickly, fall_alpha releases slowly so fans keep
# moving heat out of the heatsink after the load drops (power sheds ~96%
# of its range in ~20 s on load release while the die is still hot).
[signals.gpu_power]
enabled = false
unit = "watts"
rise_alpha = 0.5
fall_alpha = 0.1
# [[signals.gpu_power.curve]]
# value = 120
# duty = 30
# Thermal margin: headroom in °C to the card's own thermal limit
# (limit - die temp), so the curve is INVERTED — small headroom means high
# duty. Self-calibrating across cards: one margin curve is correct on any
# card without knowing that card's limit. Disabled by default; a curve is
# required when enabled (values must be strictly increasing headroom).
[signals.thermal_margin]
enabled = false
alpha = 0.4
# [[signals.thermal_margin.curve]]
# value = 5
# duty = 100
# [[signals.thermal_margin.curve]]
# value = 35
# duty = 30
# Memory junction temperature. Disabled by default because NVML does not
# expose it on most consumer cards (verified NotSupported on an RTX 5090);
# only enable it where `unifand_signal_value{signal="mem_temp"}` actually
# reports. A curve is required when enabled.
[signals.mem_temp]
enabled = false
alpha = 1.0
# [[signals.mem_temp.curve]]
# temp = 80.0
# duty = 50
# Throttle duty floor: while the GPU reports any of the listed throttle
# reasons, applied duty is raised to at least floor_duty, held for
# hold_secs after the last assertion (anti-flap). A safety net, not a
# routine path — do NOT add "sw_power_cap" to reasons: it asserts
# continuously under any sustained load (measured) and would pin fans at
# the floor for every job.
[signals.throttle]
enabled = false
floor_duty = 85
hold_secs = 30
reasons = ["sw_thermal", "hw_thermal", "hw_power_brake"]