Skip to content

Commit c04dfb2

Browse files
authored
Merge pull request #223 from labmlai/smooth
Smoothing till a point
2 parents b30d05f + d09149f commit c04dfb2

File tree

11 files changed

+93
-51
lines changed

11 files changed

+93
-51
lines changed

app/server/labml_app/analyses/preferences.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Preferences:
1515
step_range: List[int]
1616
focus_smoothed: bool
1717
smooth_value: float
18+
trim_smooth_ends: float
1819

1920
@classmethod
2021
def defaults(cls):
@@ -23,7 +24,8 @@ def defaults(cls):
2324
errors=[],
2425
step_range=[-1, -1],
2526
focus_smoothed=True,
26-
smooth_value=50 # 50% smooth
27+
smooth_value=50, # 50% smooth
28+
trim_smooth_ends=True
2729
)
2830

2931
def update_preferences(self, data: PreferencesData) -> None:
@@ -42,6 +44,9 @@ def update_preferences(self, data: PreferencesData) -> None:
4244
if 'smooth_value' in data:
4345
self.smooth_value = data['smooth_value']
4446

47+
if 'trim_smooth_ends' in data:
48+
self.trim_smooth_ends = data['trim_smooth_ends']
49+
4550
self.save()
4651

4752
def update_series_preferences(self, data: SeriesPreferences) -> None:
@@ -53,5 +58,6 @@ def get_data(self) -> Dict[str, Any]:
5358
'chart_type': self.chart_type,
5459
'step_range': self.step_range,
5560
'focus_smoothed': self.focus_smoothed,
56-
'smooth_value': self.smooth_value
61+
'smooth_value': self.smooth_value,
62+
'trim_smooth_ends': self.trim_smooth_ends
5763
}

app/ui/src/analyses/experiments/chart_wrapper/card.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {Indicator} from "../../../models/run"
33
import {
44
AnalysisPreferenceModel, ComparisonPreferenceModel,
55
} from "../../../models/preferences"
6-
import {getChartType, smoothAndTrimAllCharts, trimSteps} from "../../../components/charts/utils"
6+
import {getChartType, smoothAndTrimAllCharts} from "../../../components/charts/utils"
77
import {LineChart} from "../../../components/charts/lines/chart"
88
import {SparkLines} from "../../../components/charts/spark_lines/chart"
99

@@ -36,6 +36,7 @@ export class CardWrapper {
3636
private stepRange: number[]
3737
private focusSmoothed: boolean
3838
private smoothValue: number
39+
private trimSmoothEnds: boolean
3940

4041
private readonly title?: string
4142

@@ -73,8 +74,9 @@ export class CardWrapper {
7374
this.stepRange = preferenceData.step_range
7475
this.focusSmoothed = preferenceData.focus_smoothed
7576
this.smoothValue = preferenceData.smooth_value
77+
this.trimSmoothEnds = preferenceData.trim_smooth_ends
7678

77-
smoothAndTrimAllCharts(this.series, this.baseSeries, this.smoothValue, this.stepRange)
79+
smoothAndTrimAllCharts(this.series, this.baseSeries, this.smoothValue, this.stepRange, this.trimSmoothEnds)
7880
}
7981

8082
public render() {

app/ui/src/analyses/experiments/chart_wrapper/view.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface MetricDataStore {
3636
focusSmoothed: boolean
3737
stepRange: number[]
3838
smoothValue: number
39+
trimSmoothEnds: boolean
3940

4041
isUnsaved: boolean
4142
}
@@ -100,6 +101,12 @@ namespace ChangeHandlers {
100101
}
101102
}
102103

104+
export class TrimSmoothToggleHandler extends ChangeHandlerBase {
105+
protected handleChange() {
106+
this.wrapper.dataStore.trimSmoothEnds = !this.wrapper.dataStore.trimSmoothEnds
107+
}
108+
}
109+
103110
export class ToggleChangeHandler extends ChangeHandlerBase {
104111
private readonly idx: number
105112
private readonly isBase: boolean
@@ -153,6 +160,7 @@ export class ViewWrapper {
153160
private readonly focusButton: ToggleButton
154161
private readonly smoothSlider: Slider
155162
private readonly deleteButton: DeleteButton
163+
private readonly trimSmoothToggleButton: ToggleButton
156164
private sparkLines: SparkLines
157165

158166
public dataStore: MetricDataStore
@@ -226,6 +234,15 @@ export class ViewWrapper {
226234
parent: this.constructor.name,
227235
onButtonClick: this.onDelete
228236
})
237+
this.trimSmoothToggleButton = new ToggleButton({
238+
onButtonClick: () => {
239+
let changeHandler = new ChangeHandlers.TrimSmoothToggleHandler(this)
240+
changeHandler.change()
241+
},
242+
text: 'Trim Smooth Ends',
243+
isToggled: this.dataStore.trimSmoothEnds,
244+
parent: this.constructor.name
245+
})
229246
}
230247

231248
public clear() {
@@ -316,7 +333,8 @@ export class ViewWrapper {
316333
}
317334

318335
private smoothSeries() {
319-
smoothAndTrimAllCharts(this.dataStore.series, this.dataStore.baseSeries, this.dataStore.smoothValue, this.dataStore.stepRange)
336+
smoothAndTrimAllCharts(this.dataStore.series, this.dataStore.baseSeries,
337+
this.dataStore.smoothValue, this.dataStore.stepRange, this.dataStore.trimSmoothEnds)
320338
}
321339

322340
private renderTopButtons() {
@@ -357,6 +375,7 @@ export class ViewWrapper {
357375
$('div', '.button-row', $ => {
358376
$('span.key', 'Smoothing:')
359377
this.smoothSlider.render($)
378+
this.trimSmoothToggleButton.render($)
360379
})
361380
})
362381
}

app/ui/src/analyses/experiments/comparison/view.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class ComparisonView extends ScreenView implements MetricDataStore {
3737
focusSmoothed: boolean
3838
stepRange: number[]
3939
smoothValue: number
40+
trimSmoothEnds: boolean
4041

4142
isUnsaved: boolean
4243

@@ -98,6 +99,7 @@ class ComparisonView extends ScreenView implements MetricDataStore {
9899
this.basePlotIdx = [...this.preferenceData.base_series_preferences]
99100
}
100101
this.smoothValue = this.preferenceData.smooth_value
102+
this.trimSmoothEnds = this.preferenceData.trim_smooth_ends
101103

102104
if (!!this.baseUuid) {
103105
await this.updateBaseRun(force)
@@ -275,7 +277,8 @@ class ComparisonView extends ScreenView implements MetricDataStore {
275277
is_base_distributed: this.baseRun.world_size != 0,
276278
series_names: this.series.map(s => s.name),
277279
base_series_names: this.baseSeries ? this.baseSeries.map(s => s.name) : [],
278-
smooth_value: this.smoothValue
280+
smooth_value: this.smoothValue,
281+
trim_smooth_ends: this.trimSmoothEnds
279282
}
280283

281284
await this.preferenceCache.setPreference(preferenceData)

app/ui/src/analyses/experiments/metrics/view.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class MetricsView extends ScreenView implements MetricDataStore {
5656
stepRange: number[]
5757
smoothValue: number
5858
isUnsaved: boolean
59+
trimSmoothEnds: boolean
5960

6061
constructor(uuid: string, metricUuid?: string) {
6162
super()
@@ -91,6 +92,7 @@ class MetricsView extends ScreenView implements MetricDataStore {
9192
this.focusSmoothed = this.preferenceData.focus_smoothed
9293
this.plotIdx = [...fillPlotPreferences(this.series, this.preferenceData.series_preferences)]
9394
this.smoothValue = this.preferenceData.smooth_value
95+
this.trimSmoothEnds = this.preferenceData.trim_smooth_ends
9496
})
9597

9698
this.refresh = new AwesomeRefreshButton(this.onRefresh.bind(this))
@@ -275,7 +277,8 @@ class MetricsView extends ScreenView implements MetricDataStore {
275277
focus_smoothed: this.focusSmoothed,
276278
sub_series_preferences: undefined,
277279
series_names: this.series.map(s => s.name),
278-
smooth_value: this.smoothValue
280+
smooth_value: this.smoothValue,
281+
trim_smooth_ends: this.trimSmoothEnds
279282
}
280283

281284
if (this.metricUuid == null) {

app/ui/src/components/charts/lines/chart.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export class LineChart {
8181
this.baseSeries = this.baseSeries.filter((_, i) => this.basePlotIndex[i] == 1)
8282
this.currentSeries = this.currentSeries.filter((_, i) => this.currentPlotIndex[i] == 1)
8383

84-
const stepExtent = getExtent(this.baseSeries.concat(this.currentSeries).map(s => s.trimmedSeries), d => d.step, false, true)
84+
// get steps from series with at least one value
85+
const series_concat = this.baseSeries.concat(this.currentSeries).filter(s => s.trimmedSeries.length > 0)
86+
const stepExtent = getExtent(series_concat.map(s => s.trimmedSeries), d => d.step, false, true)
8587
this.xScale = getScale(stepExtent, this.chartWidth, false)
8688

8789
this.chartColors = new ChartColors({
@@ -93,7 +95,7 @@ export class LineChart {
9395
chartId = `chart_${Math.round(Math.random() * 1e9)}`
9496

9597
changeScale() {
96-
let plotSeries = this.baseSeries.concat(this.currentSeries).map(s => s.trimmedSeries)
98+
let plotSeries = this.baseSeries.concat(this.currentSeries).map(s => s.trimmedSeries).filter(s => s.length > 0)
9799
if (plotSeries.length == 0) {
98100
this.yScale = d3.scaleLinear()
99101
.domain([0, 0])

app/ui/src/components/charts/spark_lines/spark_line.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class SparkLine {
4141
constructor(opt: SparkLineOptions) {
4242
this.series = opt.series
4343

44-
if (opt.selected == -1) {
44+
if (opt.selected == -1 && this.series.length > 0) {
4545
this.series = [this.series[this.series.length - 1]]
4646
}
4747
this.name = opt.name
@@ -73,12 +73,16 @@ export class SparkLine {
7373

7474
changeCursorValue(cursorStep?: number | null) {
7575
if (this.isSelected) {
76-
this.linePlot.renderIndicators(cursorStep)
76+
this.linePlot?.renderIndicators(cursorStep)
7777
this.renderValue(cursorStep)
7878
}
7979
}
8080

8181
renderValue(cursorStep?: number | null) {
82+
if (this.series.length == 0) {
83+
return
84+
}
85+
8286
const index = this.isSelected ?
8387
getSelectedIdx(this.series, this.bisect, cursorStep) : this.series.length - 1
8488
const last = this.series[index]
@@ -107,6 +111,9 @@ export class SparkLine {
107111
let title = $('span', '.title', this.name, {style: {color: this.color}})
108112
let sparkline = $('svg.sparkline', {style: {width: `${this.chartWidth + this.titleWidth * 2}px`}, height: 36}, $ => {
109113
$('g', {transform: `translate(${this.titleWidth}, 30)`}, $ => {
114+
if (this.series.length == 0) {
115+
return
116+
}
110117
new LineFill({
111118
series: this.series,
112119
xScale: this.xScale,

app/ui/src/components/charts/timeseries/chart.ts

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
getTimeScale,
1010
smooth45,
1111
toDate,
12-
trimSteps
1312
} from "../utils"
1413
import {BottomTimeAxis, RightAxis} from "../axis"
1514
import {TimeSeriesFill, TimeSeriesPlot} from './plot'

app/ui/src/components/charts/utils.ts

+32-39
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,10 @@ export function getChartType(index: number): 'log' | 'linear' {
189189
* @param {Indicator[]} baseSeries - The base series of data for comparison.
190190
* @param {number} smoothValue - The value to be used for smoothing the data.
191191
* @param {number[]} stepRange - The range of steps to be considered for trimming.
192+
* @param {boolean} trimSmoothEnds - Whether to trim the smoothed ends of the series.
192193
*/
193-
export function smoothAndTrimAllCharts(series: Indicator[], baseSeries: Indicator[], smoothValue: number, stepRange: number[]) {
194+
export function smoothAndTrimAllCharts(series: Indicator[], baseSeries: Indicator[], smoothValue: number,
195+
stepRange: number[], trimSmoothEnds: boolean = true) {
194196
let [smoothWindow, smoothRange ] = getSmoothWindow(series ?? [],
195197
baseSeries ?? [], smoothValue)
196198

@@ -199,46 +201,52 @@ export function smoothAndTrimAllCharts(series: Indicator[], baseSeries: Indicato
199201
s.series = smoothSeries(s.series, smoothWindow[0][i])
200202
return s
201203
})
202-
trimSteps(series, stepRange[0], stepRange[1], smoothRange)
204+
trimSteps(series, stepRange[0], stepRange[1], smoothWindow[0], trimSmoothEnds)
203205
}
204206

205207
if (baseSeries != null) {
206208
baseSeries.map((s, i) => {
207209
s.series = smoothSeries(s.series, smoothWindow[1][i])
208210
return s
209211
})
210-
trimSteps(baseSeries, stepRange[0], stepRange[1], smoothRange)
212+
trimSteps(baseSeries, stepRange[0], stepRange[1], smoothWindow[1], trimSmoothEnds)
211213
}
212214
}
213215

214-
export function trimSteps(series: Indicator[], min: number, max: number, smoothRange: number = 0) {
215-
smoothRange /= 2 // remove half from each end
216-
series.forEach(s => {
217-
let localSmoothRange = smoothRange
218-
if (s.series.length <= 2) {
219-
localSmoothRange = 0
220-
} else {
221-
let trimStepCount = localSmoothRange / (s.series[1].step - s.series[0].step)
222-
if (trimStepCount < 1) { // not going to trim anything - or else will filter out single steps
223-
localSmoothRange = 0
224-
}
216+
function trimSteps(series: Indicator[], min: number, max: number, smoothWindow: number[], trimSmoothEnds: boolean = true) {
217+
series.forEach((s, i) => {
218+
let localSmoothWindow = smoothWindow[i] / 2 // remove half from each end
219+
localSmoothWindow = Math.floor(localSmoothWindow)
220+
if (localSmoothWindow < 0) {
221+
localSmoothWindow = 0
222+
}
223+
if (s.series.length <= 1) {
224+
localSmoothWindow = 0
225+
} else if (smoothWindow[i] >= s.series.length) {
226+
localSmoothWindow = Math.floor(s.series.length/2)
225227
}
226228

229+
227230
let localMin = min
228231
let localMax = max
229232

230233
if (localMin == -1) {
231234
localMin = s.series[0].step
232235
}
233-
localMin = Math.max(localMin, s.series[0].step + localSmoothRange)
236+
if (trimSmoothEnds) {
237+
localMin = Math.max(localMin, s.series[localSmoothWindow].step)
238+
}
234239

235240
if (localMax == -1) {
236241
localMax = s.series[s.series.length - 1].step
237242
}
238-
localMax = Math.min(localMax, s.series[s.series.length - 1].step - localSmoothRange)
243+
if (trimSmoothEnds) {
244+
localMax = Math.min(localMax, s.series[s.series.length - 1 - localSmoothWindow +
245+
(s.series.length%2 == 0 && localSmoothWindow != 0 ? 1 : 0)].step) // get the mid value for even length series
246+
}
239247

240-
localMin = Math.floor(localMin)
241-
localMax = Math.ceil(localMax)
248+
localMin = Math.floor(localMin-1)
249+
localMax = Math.ceil(localMax+1)
242250

243251
let minIndex = s.series.length - 1
244252
let maxIndex = 0
@@ -253,25 +261,6 @@ export function trimSteps(series: Indicator[], min: number, max: number, smoothR
253261

254262
s.lowTrimIndex = minIndex
255263
s.highTrimIndex = maxIndex
256-
257-
if (s.lowTrimIndex > s.highTrimIndex) { // if trim covers all just give the middle value
258-
localMax = max == -1 ? s.series[s.series.length - 1].step : max
259-
localMin = min == -1 ? s.series[0].step : min
260-
261-
minIndex = s.series.length - 1
262-
maxIndex = 0
263-
264-
for (let i = 0; i < s.series.length; i++) {
265-
let p = s.series[i]
266-
if (p.step >= localMin && p.step <= localMax) {
267-
minIndex = Math.min(i, minIndex)
268-
maxIndex = Math.max(i, maxIndex)
269-
}
270-
}
271-
272-
s.lowTrimIndex = (minIndex + maxIndex) / 2
273-
s.highTrimIndex = (minIndex + maxIndex) / 2
274-
}
275264
})
276265
}
277266

@@ -312,7 +301,9 @@ export function getSmoothWindow(currentSeries: Indicator[], baseSeries: Indicato
312301

313302
let stepRange = [[],[]]
314303
for (let s of currentSeries) {
315-
if (s.series.length >= 2 && !s.is_summary) {
304+
if (smoothValue == 100) { // hardcode to max range in case not range due to step inconsistencies
305+
stepRange[0].push(s.series.length)
306+
} else if (s.series.length >= 2 && !s.is_summary) {
316307
let stepGap = s.series[1].step - s.series[0].step
317308
let numSteps = Math.max(1, Math.floor(smoothRange / stepGap))
318309
stepRange[0].push(numSteps)
@@ -321,7 +312,9 @@ export function getSmoothWindow(currentSeries: Indicator[], baseSeries: Indicato
321312
}
322313
}
323314
for (let s of baseSeries) {
324-
if (s.series.length >= 2 && !s.is_summary) {
315+
if (smoothValue == 100) {
316+
stepRange[1].push(s.series.length)
317+
} else if (s.series.length >= 2 && !s.is_summary) {
325318
let stepGap = s.series[1].step - s.series[0].step
326319
let numSteps = Math.max(1, Math.floor(smoothRange / stepGap))
327320
stepRange[1].push(numSteps)

app/ui/src/models/preferences.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface AnalysisPreferenceBaseModel {
66
step_range: number[]
77
focus_smoothed: boolean
88
smooth_value: number
9+
trim_smooth_ends: boolean
910
}
1011

1112
export interface AnalysisPreferenceModel extends AnalysisPreferenceBaseModel {

app/ui/src/style.scss

+7
Original file line numberDiff line numberDiff line change
@@ -1576,4 +1576,11 @@ input[type=number] {
15761576

15771577
.relative {
15781578
position: relative;
1579+
}
1580+
1581+
pre {
1582+
user-select: text !important;
1583+
-moz-user-select: text !important;
1584+
-ms-user-select: text !important;
1585+
-webkit-user-select: text !important;
15791586
}

0 commit comments

Comments
 (0)