Skip to content

Commit 78dbba7

Browse files
Quentin-Mclaude
andcommitted
proxy: parallelize startup preload within process groups
Models in the same group now preload concurrently via goroutines instead of sequentially. Groups are still loaded in order to respect exclusivity. This significantly reduces startup time when a group has multiple models (e.g., an LLM + STT + TTS that share GPU resources). Each model's container starts and health-checks simultaneously. - Group preload models by ProcessGroup before starting - Use sync.WaitGroup to parallelize within each group - Call swapProcessGroup once per group (handles exclusivity) - Log group completion with model count Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f5f44c6 commit 78dbba7

1 file changed

Lines changed: 46 additions & 18 deletions

File tree

proxy/proxymanager.go

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -175,35 +175,63 @@ func New(proxyConfig config.Config) *ProxyManager {
175175

176176
// run any startup hooks
177177
if len(proxyConfig.Hooks.OnStartup.Preload) > 0 {
178-
// do it in the background, don't block startup -- not sure if good idea yet
178+
// do it in the background, don't block startup
179179
go func() {
180-
discardWriter := &DiscardWriter{}
180+
// Group preload models by process group so models in the same
181+
// group can be started in parallel (e.g. STT + TTS on the same GPU).
182+
// Groups are preloaded sequentially to respect exclusivity.
183+
type groupEntry struct {
184+
id string
185+
models []string
186+
}
187+
var groupOrder []groupEntry
188+
seen := map[string]int{} // groupID -> index into groupOrder
189+
181190
for _, preloadModelName := range proxyConfig.Hooks.OnStartup.Preload {
182191
modelID, ok := proxyConfig.RealModelName(preloadModelName)
183-
184192
if !ok {
185193
proxyLogger.Warnf("Preload model %s not found in config", preloadModelName)
186194
continue
187195
}
196+
pg := pm.findGroupByModelName(modelID)
197+
if pg == nil {
198+
proxyLogger.Warnf("Preload model %s has no process group", modelID)
199+
continue
200+
}
201+
if idx, exists := seen[pg.id]; exists {
202+
groupOrder[idx].models = append(groupOrder[idx].models, modelID)
203+
} else {
204+
seen[pg.id] = len(groupOrder)
205+
groupOrder = append(groupOrder, groupEntry{id: pg.id, models: []string{modelID}})
206+
}
207+
}
188208

189-
proxyLogger.Infof("Preloading model: %s", modelID)
190-
processGroup, err := pm.swapProcessGroup(modelID)
191-
209+
for _, ge := range groupOrder {
210+
// Swap to this group (handles exclusivity — idles other groups)
211+
processGroup, err := pm.swapProcessGroup(ge.models[0])
192212
if err != nil {
193-
event.Emit(ModelPreloadedEvent{
194-
ModelName: modelID,
195-
Success: false,
196-
})
197-
proxyLogger.Errorf("Failed to preload model %s: %v", modelID, err)
213+
for _, mid := range ge.models {
214+
event.Emit(ModelPreloadedEvent{ModelName: mid, Success: false})
215+
proxyLogger.Errorf("Failed to preload model %s: %v", mid, err)
216+
}
198217
continue
199-
} else {
200-
req, _ := http.NewRequest("GET", "/", nil)
201-
processGroup.ProxyRequest(modelID, discardWriter, req)
202-
event.Emit(ModelPreloadedEvent{
203-
ModelName: modelID,
204-
Success: true,
205-
})
206218
}
219+
220+
// Start all models in this group in parallel
221+
var wg sync.WaitGroup
222+
for _, modelID := range ge.models {
223+
wg.Add(1)
224+
go func(mid string) {
225+
defer wg.Done()
226+
proxyLogger.Infof("Preloading model: %s", mid)
227+
req, _ := http.NewRequest("GET", "/", nil)
228+
processGroup.ProxyRequest(mid, &DiscardWriter{}, req)
229+
event.Emit(ModelPreloadedEvent{ModelName: mid, Success: true})
230+
proxyLogger.Infof("Preloaded model: %s", mid)
231+
}(modelID)
232+
}
233+
wg.Wait()
234+
proxyLogger.Infof("Preloaded group %s (%d models)", ge.id, len(ge.models))
207235
}
208236
}()
209237
}

0 commit comments

Comments
 (0)