Skip to content

Commit 544ae09

Browse files
authored
HParams: Update tb_http_client to support the hparams plugin backend take 2 (#6340)
## Motivation for features / changes As part of the effort to surface hparams data in the time series dashboard we need to start actually fetching it. The logic existed internally but for some reason was not previously available in OSS. I originally attempted this in #6318 but due to incompatibilities with internal Colab had to revert it in #6336. The issue was that Colab does not allow `POST` requests and our method of mapping POST requests to GET requests didn't meet the expectations of the hparams_plugin backend. I've improved the tb_http_client in #6337 to now work with the hparams_plugin backend so this should now be fine ## Technical description of changes I mostly copied the internal code to the oss implementation and made a few small adjustments to imports. The biggest change is that I now utilize the `serializeUnder` parameter of our custom POST function to ensure the requests meet the expectations of the hparams backend ## Screenshots of UI changes ### enableHparamsInTimeSeries=false&tensorboardColab=false ![image](https://user-images.githubusercontent.com/78179109/233498859-df60aa0b-43d0-486d-8e4a-5be8fab7d08e.png) ### enableHparamsInTimeSeries=true&tensorboardColab=false ![image](https://user-images.githubusercontent.com/78179109/233498895-01e6e773-faf7-41de-9e34-199925a3e80f.png) ### enableHparamsInTimeSeries=false&tensorboardColab=true ![image](https://user-images.githubusercontent.com/78179109/233498942-fc414b81-256b-4762-837b-56148dc1dbcf.png) ### enableHparamsInTimeSeries=true&tensorboardColab=true ![image](https://user-images.githubusercontent.com/78179109/233498974-c9fd8555-db2a-49e4-9188-b7c19cfaaddf.png) ## Detailed steps to verify changes work correctly (as executed by you) I ensured all of the above states result in the runs table being displayed properly and that no errors are thrown by the hparams plugin backend
1 parent 33bd8a4 commit 544ae09

File tree

4 files changed

+639
-11
lines changed

4 files changed

+639
-11
lines changed

tensorboard/webapp/runs/data_source/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ tf_ts_library(
4848
"runs_data_source_test.ts",
4949
],
5050
deps = [
51+
":backend_types",
5152
":data_source",
53+
":testing",
5254
"//tensorboard/webapp/angular:expect_angular_core_testing",
5355
"//tensorboard/webapp/webapp_data_source:http_client_testing",
5456
"@npm//@types/jasmine",

tensorboard/webapp/runs/data_source/runs_data_source.ts

+164-9
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,71 @@ See the License for the specific language governing permissions and
1313
limitations under the License.
1414
==============================================================================*/
1515
import {Injectable} from '@angular/core';
16-
import {Observable, of} from 'rxjs';
17-
import {map} from 'rxjs/operators';
18-
import {TBHttpClient} from '../../webapp_data_source/tb_http_client';
16+
import {Observable, of, throwError} from 'rxjs';
17+
import {catchError, map, mergeMap} from 'rxjs/operators';
1918
import {
19+
HttpErrorResponse,
20+
TBHttpClient,
21+
} from '../../webapp_data_source/tb_http_client';
22+
import * as backendTypes from './runs_backend_types';
23+
import {
24+
Domain,
25+
DomainType,
2026
HparamsAndMetadata,
27+
HparamSpec,
28+
HparamValue,
29+
MetricSpec,
2130
Run,
2231
RunsDataSource,
32+
RunToHparamsAndMetrics,
2333
} from './runs_data_source_types';
2434

35+
const HPARAMS_HTTP_PATH_PREFIX = 'data/plugin/hparams';
36+
2537
type BackendGetRunsResponse = string[];
2638

2739
function runToRunId(run: string, experimentId: string) {
2840
return `${experimentId}/${run}`;
2941
}
3042

43+
function transformBackendHparamSpec(
44+
hparamInfo: backendTypes.HparamSpec
45+
): HparamSpec {
46+
let domain: Domain;
47+
if (backendTypes.isDiscreteDomainHparamSpec(hparamInfo)) {
48+
domain = {type: DomainType.DISCRETE, values: hparamInfo.domainDiscrete};
49+
} else if (backendTypes.isIntervalDomainHparamSpec(hparamInfo)) {
50+
domain = {...hparamInfo.domainInterval, type: DomainType.INTERVAL};
51+
} else {
52+
domain = {
53+
type: DomainType.INTERVAL,
54+
minValue: -Infinity,
55+
maxValue: Infinity,
56+
};
57+
}
58+
return {
59+
description: hparamInfo.description,
60+
displayName: hparamInfo.displayName,
61+
name: hparamInfo.name,
62+
type: hparamInfo.type,
63+
domain,
64+
};
65+
}
66+
67+
function transformBackendMetricSpec(
68+
metricInfo: backendTypes.MetricSpec
69+
): MetricSpec {
70+
const {name, ...otherSpec} = metricInfo;
71+
return {
72+
...otherSpec,
73+
tag: name.tag,
74+
};
75+
}
76+
77+
declare interface GetExperimentHparamRequestPayload {
78+
experimentName: string;
79+
}
80+
3181
@Injectable()
3282
export class TBRunsDataSource implements RunsDataSource {
3383
constructor(private readonly http: TBHttpClient) {}
@@ -48,11 +98,116 @@ export class TBRunsDataSource implements RunsDataSource {
4898
}
4999

50100
fetchHparamsMetadata(experimentId: string): Observable<HparamsAndMetadata> {
51-
// Return a stub implementation.
52-
return of({
53-
hparamSpecs: [],
54-
metricSpecs: [],
55-
runToHparamsAndMetrics: {},
56-
});
101+
const requestPayload: GetExperimentHparamRequestPayload = {
102+
experimentName: experimentId,
103+
};
104+
return this.http
105+
.post<backendTypes.BackendHparamsExperimentResponse>(
106+
`/experiment/${experimentId}/${HPARAMS_HTTP_PATH_PREFIX}/experiment`,
107+
requestPayload,
108+
{},
109+
'request'
110+
)
111+
.pipe(
112+
map((response) => {
113+
const colParams: backendTypes.BackendListSessionGroupRequest['colParams'] =
114+
[];
115+
116+
for (const hparamInfo of response.hparamInfos) {
117+
colParams.push({hparam: hparamInfo.name});
118+
}
119+
for (const metricInfo of response.metricInfos) {
120+
colParams.push({metric: metricInfo.name});
121+
}
122+
123+
const listSessionRequestParams: backendTypes.BackendListSessionGroupRequest =
124+
{
125+
experimentName: experimentId,
126+
allowedStatuses: [
127+
backendTypes.RunStatus.STATUS_FAILURE,
128+
backendTypes.RunStatus.STATUS_RUNNING,
129+
backendTypes.RunStatus.STATUS_SUCCESS,
130+
backendTypes.RunStatus.STATUS_UNKNOWN,
131+
],
132+
colParams,
133+
startIndex: 0,
134+
// arbitrary large number so it does not get clipped.
135+
sliceSize: 1e6,
136+
};
137+
138+
return {
139+
experimentHparamsInfo: response,
140+
listSessionRequestParams,
141+
};
142+
}),
143+
mergeMap(({experimentHparamsInfo, listSessionRequestParams}) => {
144+
return this.http
145+
.post<backendTypes.BackendListSessionGroupResponse>(
146+
`/experiment/${experimentId}/${HPARAMS_HTTP_PATH_PREFIX}/session_groups`,
147+
listSessionRequestParams,
148+
{},
149+
'request'
150+
)
151+
.pipe(
152+
map((sessionGroupsList) => {
153+
return {experimentHparamsInfo, sessionGroupsList};
154+
})
155+
);
156+
}),
157+
map(({experimentHparamsInfo, sessionGroupsList}) => {
158+
const runToHparamsAndMetrics: RunToHparamsAndMetrics = {};
159+
160+
// Reorganize the sessionGroup/session into run to <hparams,
161+
// metrics>.
162+
for (const sessionGroup of sessionGroupsList.sessionGroups) {
163+
const hparams: HparamValue[] = Object.entries(
164+
sessionGroup.hparams
165+
).map((keyValue) => {
166+
const [hparam, value] = keyValue;
167+
return {name: hparam, value};
168+
});
169+
170+
for (const session of sessionGroup.sessions) {
171+
for (const metricValue of session.metricValues) {
172+
const runName = metricValue.name.group
173+
? `${session.name}/${metricValue.name.group}`
174+
: session.name;
175+
const runId = `${experimentId}/${runName}`;
176+
const hparamsAndMetrics = runToHparamsAndMetrics[runId] || {
177+
metrics: [],
178+
hparams,
179+
};
180+
hparamsAndMetrics.metrics.push({
181+
tag: metricValue.name.tag,
182+
trainingStep: metricValue.trainingStep,
183+
value: metricValue.value,
184+
});
185+
runToHparamsAndMetrics[runId] = hparamsAndMetrics;
186+
}
187+
}
188+
}
189+
return {
190+
hparamSpecs: experimentHparamsInfo.hparamInfos.map(
191+
transformBackendHparamSpec
192+
),
193+
metricSpecs: experimentHparamsInfo.metricInfos.map(
194+
transformBackendMetricSpec
195+
),
196+
runToHparamsAndMetrics,
197+
};
198+
}),
199+
catchError((error) => {
200+
// HParams plugin return 400 when there are no hparams for an
201+
// experiment.
202+
if (error instanceof HttpErrorResponse && error.status === 400) {
203+
return of({
204+
hparamSpecs: [],
205+
metricSpecs: [],
206+
runToHparamsAndMetrics: {},
207+
});
208+
}
209+
return throwError(error);
210+
})
211+
);
57212
}
58213
}

0 commit comments

Comments
 (0)