mirror of
https://github.com/hanzo-ml/katib.git
synced 2026-09-24 00:31:27 +00:00
suggestion: Refactor (#21)
- Fix a typo in hyperband service `MaxParallel` - Move the service to the suggestion package - Move the port to the suggestion package Signed-off-by: Ce Gao <[email protected]>
This commit is contained in:
+8
-176
@@ -1,192 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
pb "github.com/kubeflow/hp-tuning/api"
|
||||
"github.com/kubeflow/hp-tuning/suggestion"
|
||||
)
|
||||
|
||||
const (
|
||||
port = "0.0.0.0:6789"
|
||||
)
|
||||
|
||||
type GridSuggestParameters struct {
|
||||
defaultGridNum int
|
||||
gridConfig map[string]int
|
||||
MaxParallel int
|
||||
}
|
||||
|
||||
type GridSuggestService struct {
|
||||
parameters map[string]*GridSuggestParameters
|
||||
grids map[string][][]*api.Parameter
|
||||
gridPointer map[string]int
|
||||
}
|
||||
|
||||
func NewGridSuggestService() *GridSuggestService {
|
||||
return &GridSuggestService{parameters: make(map[string]*GridSuggestParameters), grids: make(map[string][][]*api.Parameter), gridPointer: make(map[string]int)}
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) SetSuggestionParameters(ctx context.Context, in *api.SetSuggestionParametersRequest) (*api.SetSuggestionParametersReply, error) {
|
||||
p := &GridSuggestParameters{gridConfig: make(map[string]int)}
|
||||
for _, sp := range in.SuggestionParameters {
|
||||
switch sp.Name {
|
||||
case "DefaultGrid":
|
||||
p.defaultGridNum, _ = strconv.Atoi(sp.Value)
|
||||
case "MaxParrallel":
|
||||
p.MaxParallel, _ = strconv.Atoi(sp.Value)
|
||||
default:
|
||||
p.gridConfig[sp.Name], _ = strconv.Atoi(sp.Value)
|
||||
}
|
||||
}
|
||||
s.parameters[in.StudyId] = p
|
||||
return &api.SetSuggestionParametersReply{}, nil
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) allocInt(min int, max int, reqnum int) []string {
|
||||
ret := make([]string, reqnum)
|
||||
if reqnum == 1 {
|
||||
ret[0] = strconv.Itoa(min)
|
||||
} else {
|
||||
for i := 0; i < reqnum; i++ {
|
||||
ret[i] = strconv.Itoa(min + ((max - min) * i / (reqnum - 1)))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) allocFloat(min float64, max float64, reqnum int) []string {
|
||||
ret := make([]string, reqnum)
|
||||
if reqnum == 1 {
|
||||
ret[0] = strconv.FormatFloat(min, 'f', 4, 64)
|
||||
} else {
|
||||
for i := 0; i < reqnum; i++ {
|
||||
ret[i] = strconv.FormatFloat(min+(((max-min)/float64(reqnum-1))*float64(i)), 'f', 4, 64)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) allocCat(list []string, reqnum int) []string {
|
||||
ret := make([]string, reqnum)
|
||||
if reqnum == 1 {
|
||||
ret[0] = list[0]
|
||||
} else {
|
||||
for i := 0; i < reqnum; i++ {
|
||||
ret[i] = list[(((len(list) - 1) * i) / (reqnum - 1))]
|
||||
fmt.Printf("ret %v %v\n", i, ret[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) setP(gci int, p [][]*api.Parameter, pg [][]string, pcs []*api.ParameterConfig) {
|
||||
if gci == len(pg)-1 {
|
||||
for i := range pg[gci] {
|
||||
p[i] = append(p[i], &api.Parameter{
|
||||
Name: pcs[gci].Name,
|
||||
ParameterType: pcs[gci].ParameterType,
|
||||
Value: pg[gci][i],
|
||||
})
|
||||
|
||||
}
|
||||
return
|
||||
} else {
|
||||
d := len(p) / len(pg[gci])
|
||||
for i := range pg[gci] {
|
||||
for j := d * i; j < d*(i+1); j++ {
|
||||
p[j] = append(p[j], &api.Parameter{
|
||||
Name: pcs[gci].Name,
|
||||
ParameterType: pcs[gci].ParameterType,
|
||||
Value: pg[gci][i],
|
||||
})
|
||||
}
|
||||
s.setP(gci+1, p[d*i:d*(i+1)], pg, pcs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) genGrids(studyId string, pcs []*api.ParameterConfig) [][]*api.Parameter {
|
||||
var pg [][]string
|
||||
var holenum int = 1
|
||||
gcl := make([]int, len(pcs))
|
||||
for i, pc := range pcs {
|
||||
gc, ok := s.parameters[studyId].gridConfig[pc.Name]
|
||||
if !ok {
|
||||
gc = s.parameters[studyId].defaultGridNum
|
||||
}
|
||||
holenum *= gc
|
||||
gcl[i] = gc
|
||||
switch pc.ParameterType {
|
||||
case api.ParameterType_INT:
|
||||
imin, _ := strconv.Atoi(pc.Feasible.Min)
|
||||
imax, _ := strconv.Atoi(pc.Feasible.Max)
|
||||
pg = append(pg, s.allocInt(imin, imax, gc))
|
||||
case api.ParameterType_DOUBLE:
|
||||
dmin, _ := strconv.ParseFloat(pc.Feasible.Min, 64)
|
||||
dmax, _ := strconv.ParseFloat(pc.Feasible.Max, 64)
|
||||
pg = append(pg, s.allocFloat(dmin, dmax, gc))
|
||||
case api.ParameterType_CATEGORICAL:
|
||||
pg = append(pg, s.allocCat(pc.Feasible.List, gc))
|
||||
}
|
||||
}
|
||||
ret := make([][]*api.Parameter, holenum)
|
||||
s.setP(0, ret, pg, pcs)
|
||||
log.Printf("Study %v : %v parameters generated", studyId, holenum)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) GenerateTrials(ctx context.Context, in *api.GenerateTrialsRequest) (*api.GenerateTrialsReply, error) {
|
||||
if _, ok := s.grids[in.StudyId]; !ok {
|
||||
s.grids[in.StudyId] = s.genGrids(in.StudyId, in.Configs.ParameterConfigs.Configs)
|
||||
s.gridPointer[in.StudyId] = 0
|
||||
}
|
||||
if s.gridPointer[in.StudyId] >= len(s.grids[in.StudyId]) {
|
||||
if len(in.RunningTrials) == 0 {
|
||||
s.StopSuggestion(ctx, &api.StopSuggestionRequest{StudyId: in.StudyId})
|
||||
return &api.GenerateTrialsReply{Completed: true}, nil
|
||||
} else {
|
||||
return &api.GenerateTrialsReply{Completed: false}, nil
|
||||
}
|
||||
}
|
||||
var reqnum int = 0
|
||||
if s.parameters[in.StudyId].MaxParallel <= 0 {
|
||||
reqnum = len(s.grids[in.StudyId])
|
||||
} else if len(s.grids[in.StudyId])-s.gridPointer[in.StudyId] < s.parameters[in.StudyId].MaxParallel-len(in.RunningTrials) {
|
||||
reqnum = len(s.grids[in.StudyId]) - s.gridPointer[in.StudyId]
|
||||
} else if len(in.RunningTrials) < s.parameters[in.StudyId].MaxParallel {
|
||||
reqnum = s.parameters[in.StudyId].MaxParallel - len(in.RunningTrials)
|
||||
}
|
||||
s_t := make([]*api.Trial, reqnum)
|
||||
for i := 0; i < int(reqnum); i++ {
|
||||
s_t[i] = &api.Trial{}
|
||||
s_t[i].Status = api.TrialState_PENDING
|
||||
s_t[i].EvalLogs = make([]*api.EvaluationLog, 0)
|
||||
s_t[i].ParameterSet = s.grids[in.StudyId][s.gridPointer[in.StudyId]+i]
|
||||
}
|
||||
s.gridPointer[in.StudyId] += reqnum
|
||||
return &api.GenerateTrialsReply{Trials: s_t, Completed: false}, nil
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) StopSuggestion(ctx context.Context, in *api.StopSuggestionRequest) (*api.StopSuggestionReply, error) {
|
||||
delete(s.gridPointer, in.StudyId)
|
||||
delete(s.grids, in.StudyId)
|
||||
delete(s.parameters, in.StudyId)
|
||||
return &api.StopSuggestionReply{}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", port)
|
||||
listener, err := net.Listen("tcp", suggestion.DefaultPort)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
size := 1<<31 - 1
|
||||
s := grpc.NewServer(grpc.MaxRecvMsgSize(size), grpc.MaxSendMsgSize(size))
|
||||
api.RegisterSuggestionServer(s, NewGridSuggestService())
|
||||
pb.RegisterSuggestionServer(s, suggestion.NewGridSuggestService())
|
||||
reflection.Register(s)
|
||||
log.Printf("Grid Search Suggestion Service\n")
|
||||
if err = s.Serve(listener); err != nil {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package suggestion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
)
|
||||
|
||||
type GridSuggestParameters struct {
|
||||
defaultGridNum int
|
||||
gridConfig map[string]int
|
||||
MaxParallel int
|
||||
}
|
||||
|
||||
type GridSuggestService struct {
|
||||
parameters map[string]*GridSuggestParameters
|
||||
grids map[string][][]*api.Parameter
|
||||
gridPointer map[string]int
|
||||
}
|
||||
|
||||
func NewGridSuggestService() *GridSuggestService {
|
||||
return &GridSuggestService{parameters: make(map[string]*GridSuggestParameters), grids: make(map[string][][]*api.Parameter), gridPointer: make(map[string]int)}
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) SetSuggestionParameters(ctx context.Context, in *api.SetSuggestionParametersRequest) (*api.SetSuggestionParametersReply, error) {
|
||||
p := &GridSuggestParameters{gridConfig: make(map[string]int)}
|
||||
for _, sp := range in.SuggestionParameters {
|
||||
switch sp.Name {
|
||||
case "DefaultGrid":
|
||||
p.defaultGridNum, _ = strconv.Atoi(sp.Value)
|
||||
case "MaxParallel":
|
||||
p.MaxParallel, _ = strconv.Atoi(sp.Value)
|
||||
default:
|
||||
p.gridConfig[sp.Name], _ = strconv.Atoi(sp.Value)
|
||||
}
|
||||
}
|
||||
s.parameters[in.StudyId] = p
|
||||
return &api.SetSuggestionParametersReply{}, nil
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) allocInt(min int, max int, reqnum int) []string {
|
||||
ret := make([]string, reqnum)
|
||||
if reqnum == 1 {
|
||||
ret[0] = strconv.Itoa(min)
|
||||
} else {
|
||||
for i := 0; i < reqnum; i++ {
|
||||
ret[i] = strconv.Itoa(min + ((max - min) * i / (reqnum - 1)))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) allocFloat(min float64, max float64, reqnum int) []string {
|
||||
ret := make([]string, reqnum)
|
||||
if reqnum == 1 {
|
||||
ret[0] = strconv.FormatFloat(min, 'f', 4, 64)
|
||||
} else {
|
||||
for i := 0; i < reqnum; i++ {
|
||||
ret[i] = strconv.FormatFloat(min+(((max-min)/float64(reqnum-1))*float64(i)), 'f', 4, 64)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) allocCat(list []string, reqnum int) []string {
|
||||
ret := make([]string, reqnum)
|
||||
if reqnum == 1 {
|
||||
ret[0] = list[0]
|
||||
} else {
|
||||
for i := 0; i < reqnum; i++ {
|
||||
ret[i] = list[(((len(list) - 1) * i) / (reqnum - 1))]
|
||||
fmt.Printf("ret %v %v\n", i, ret[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) setP(gci int, p [][]*api.Parameter, pg [][]string, pcs []*api.ParameterConfig) {
|
||||
if gci == len(pg)-1 {
|
||||
for i := range pg[gci] {
|
||||
p[i] = append(p[i], &api.Parameter{
|
||||
Name: pcs[gci].Name,
|
||||
ParameterType: pcs[gci].ParameterType,
|
||||
Value: pg[gci][i],
|
||||
})
|
||||
|
||||
}
|
||||
return
|
||||
} else {
|
||||
d := len(p) / len(pg[gci])
|
||||
for i := range pg[gci] {
|
||||
for j := d * i; j < d*(i+1); j++ {
|
||||
p[j] = append(p[j], &api.Parameter{
|
||||
Name: pcs[gci].Name,
|
||||
ParameterType: pcs[gci].ParameterType,
|
||||
Value: pg[gci][i],
|
||||
})
|
||||
}
|
||||
s.setP(gci+1, p[d*i:d*(i+1)], pg, pcs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) genGrids(studyId string, pcs []*api.ParameterConfig) [][]*api.Parameter {
|
||||
var pg [][]string
|
||||
var holenum = 1
|
||||
gcl := make([]int, len(pcs))
|
||||
for i, pc := range pcs {
|
||||
gc, ok := s.parameters[studyId].gridConfig[pc.Name]
|
||||
if !ok {
|
||||
gc = s.parameters[studyId].defaultGridNum
|
||||
}
|
||||
holenum *= gc
|
||||
gcl[i] = gc
|
||||
switch pc.ParameterType {
|
||||
case api.ParameterType_INT:
|
||||
imin, _ := strconv.Atoi(pc.Feasible.Min)
|
||||
imax, _ := strconv.Atoi(pc.Feasible.Max)
|
||||
pg = append(pg, s.allocInt(imin, imax, gc))
|
||||
case api.ParameterType_DOUBLE:
|
||||
dmin, _ := strconv.ParseFloat(pc.Feasible.Min, 64)
|
||||
dmax, _ := strconv.ParseFloat(pc.Feasible.Max, 64)
|
||||
pg = append(pg, s.allocFloat(dmin, dmax, gc))
|
||||
case api.ParameterType_CATEGORICAL:
|
||||
pg = append(pg, s.allocCat(pc.Feasible.List, gc))
|
||||
}
|
||||
}
|
||||
ret := make([][]*api.Parameter, holenum)
|
||||
s.setP(0, ret, pg, pcs)
|
||||
log.Printf("Study %v : %v parameters generated", studyId, holenum)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) GenerateTrials(ctx context.Context, in *api.GenerateTrialsRequest) (*api.GenerateTrialsReply, error) {
|
||||
if _, ok := s.grids[in.StudyId]; !ok {
|
||||
s.grids[in.StudyId] = s.genGrids(in.StudyId, in.Configs.ParameterConfigs.Configs)
|
||||
s.gridPointer[in.StudyId] = 0
|
||||
}
|
||||
if s.gridPointer[in.StudyId] >= len(s.grids[in.StudyId]) {
|
||||
if len(in.RunningTrials) == 0 {
|
||||
s.StopSuggestion(ctx, &api.StopSuggestionRequest{StudyId: in.StudyId})
|
||||
return &api.GenerateTrialsReply{Completed: true}, nil
|
||||
} else {
|
||||
return &api.GenerateTrialsReply{Completed: false}, nil
|
||||
}
|
||||
}
|
||||
var reqnum = 0
|
||||
if s.parameters[in.StudyId].MaxParallel <= 0 {
|
||||
reqnum = len(s.grids[in.StudyId])
|
||||
} else if len(s.grids[in.StudyId])-s.gridPointer[in.StudyId] < s.parameters[in.StudyId].MaxParallel-len(in.RunningTrials) {
|
||||
reqnum = len(s.grids[in.StudyId]) - s.gridPointer[in.StudyId]
|
||||
} else if len(in.RunningTrials) < s.parameters[in.StudyId].MaxParallel {
|
||||
reqnum = s.parameters[in.StudyId].MaxParallel - len(in.RunningTrials)
|
||||
}
|
||||
trials := make([]*api.Trial, reqnum)
|
||||
for i := 0; i < int(reqnum); i++ {
|
||||
trials[i] = &api.Trial{}
|
||||
trials[i].Status = api.TrialState_PENDING
|
||||
trials[i].EvalLogs = make([]*api.EvaluationLog, 0)
|
||||
trials[i].ParameterSet = s.grids[in.StudyId][s.gridPointer[in.StudyId]+i]
|
||||
}
|
||||
s.gridPointer[in.StudyId] += reqnum
|
||||
return &api.GenerateTrialsReply{Trials: trials, Completed: false}, nil
|
||||
}
|
||||
|
||||
func (s *GridSuggestService) StopSuggestion(ctx context.Context, in *api.StopSuggestionRequest) (*api.StopSuggestionReply, error) {
|
||||
delete(s.gridPointer, in.StudyId)
|
||||
delete(s.grids, in.StudyId)
|
||||
delete(s.parameters, in.StudyId)
|
||||
return &api.StopSuggestionReply{}, nil
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
pb "github.com/kubeflow/hp-tuning/api"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
port = "0.0.0.0:6789"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
pb "github.com/kubeflow/hp-tuning/api"
|
||||
"github.com/kubeflow/hp-tuning/suggestion"
|
||||
)
|
||||
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", port)
|
||||
listener, err := net.Listen("tcp", suggestion.DefaultPort)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
size := 1<<31 - 1
|
||||
s := grpc.NewServer(grpc.MaxRecvMsgSize(size), grpc.MaxSendMsgSize(size))
|
||||
pb.RegisterSuggestionServer(s, NewHyperBandSuggestService())
|
||||
pb.RegisterSuggestionServer(s, suggestion.NewHyperBandSuggestService())
|
||||
reflection.Register(s)
|
||||
log.Printf("HyperBand Suggestion Service\n")
|
||||
log.Println("HyperBand Suggestion Service")
|
||||
if err = s.Serve(listener); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package main
|
||||
package suggestion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
"github.com/kubeflow/hp-tuning/suggestion"
|
||||
"log"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
)
|
||||
|
||||
type Bracket []*api.Trial
|
||||
@@ -41,7 +41,7 @@ type HyperBandParameters struct {
|
||||
ResourceName string
|
||||
}
|
||||
type HyperBandSuggestService struct {
|
||||
suggestion.RandomSuggestService
|
||||
RandomSuggestService
|
||||
parameters map[string]*HyperBandParameters
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
pb "github.com/kubeflow/hp-tuning/api"
|
||||
"github.com/kubeflow/hp-tuning/suggestion"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
port = "0.0.0.0:6789"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
pb "github.com/kubeflow/hp-tuning/api"
|
||||
"github.com/kubeflow/hp-tuning/suggestion"
|
||||
)
|
||||
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", port)
|
||||
listener, err := net.Listen("tcp", suggestion.DefaultPort)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
@@ -22,7 +20,7 @@ func main() {
|
||||
s := grpc.NewServer(grpc.MaxRecvMsgSize(size), grpc.MaxSendMsgSize(size))
|
||||
pb.RegisterSuggestionServer(s, suggestion.NewRandomSuggestService())
|
||||
reflection.Register(s)
|
||||
log.Printf("Random Suggestion Service\n")
|
||||
log.Println("Random Suggestion Service")
|
||||
if err = s.Serve(listener); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
|
||||
@@ -2,22 +2,19 @@ package suggestion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SuggestService interface {
|
||||
SetSuggestionParameters(ctx context.Context, in *api.SetSuggestionParametersRequest) (*api.SetSuggestionParametersReply, error)
|
||||
GenerateTrials(ctx context.Context, in *api.GenerateTrialsRequest) (*api.GenerateTrialsReply, error)
|
||||
}
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
)
|
||||
|
||||
type RandomSuggestParameters struct {
|
||||
SuggestionNum int
|
||||
MaxParallel int
|
||||
}
|
||||
|
||||
type RandomSuggestService struct {
|
||||
parameters map[string]*RandomSuggestParameters
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package suggestion
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kubeflow/hp-tuning/api"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPort is the port to serve the suggestion service.
|
||||
DefaultPort = "0.0.0.0:6789"
|
||||
)
|
||||
|
||||
// SuggestService is the interface for suggestion service.
|
||||
type SuggestService interface {
|
||||
SetSuggestionParameters(ctx context.Context, in *api.SetSuggestionParametersRequest) (*api.SetSuggestionParametersReply, error)
|
||||
GenerateTrials(ctx context.Context, in *api.GenerateTrialsRequest) (*api.GenerateTrialsReply, error)
|
||||
}
|
||||
Reference in New Issue
Block a user