mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-06 07:34:04 +08:00
148 lines
5.3 KiB
Go
148 lines
5.3 KiB
Go
|
|
//
|
||
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||
|
|
//
|
||
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
// you may not use this file except in compliance with the License.
|
||
|
|
// You may obtain a copy of the License at
|
||
|
|
//
|
||
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
//
|
||
|
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
// See the License for the specific language governing permissions and
|
||
|
|
// limitations under the License.
|
||
|
|
|
||
|
|
package knowledge_compile
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"os"
|
||
|
|
"runtime"
|
||
|
|
"strconv"
|
||
|
|
|
||
|
|
"ragflow/internal/utility"
|
||
|
|
)
|
||
|
|
|
||
|
|
// compilerJob is one unit of knowledge-compilation work (an I/O- or
|
||
|
|
// LLM-bounded task) executed on the shared global pool. It is a type alias for
|
||
|
|
// func() error so callers can pass plain []func() error slices without a cast.
|
||
|
|
type compilerJob = func() error
|
||
|
|
|
||
|
|
// compilerPool is the process-wide bounded worker pool that drives cross-doc
|
||
|
|
// concurrency for every knowledge-compilation stage: the DocEngine KNN pass in
|
||
|
|
// processBatch, the LLM merge-decision batches inside DecideBatch, and the
|
||
|
|
// merged-product writes/deletes. It mirrors internal/ingestion/component/
|
||
|
|
// extractor.go's extractorPool: held globally so every Consumer invocation
|
||
|
|
// shares one rate limiter instead of spinning up a pool per batch. The pool
|
||
|
|
// only bounds concurrency (it is never StopWait'd), so concurrent processBatch
|
||
|
|
// calls do not disturb each other — each call tracks completion with its own
|
||
|
|
// WaitGroup + first-error collection.
|
||
|
|
//
|
||
|
|
// The fixed size is the host vCPU count: the stages are docengine-bounded
|
||
|
|
// (KNN / write / delete) or LLM-bounded (merge decisions) rather than
|
||
|
|
// CPU-bounded, so the degree of useful parallelism is capped by the number of
|
||
|
|
// available cores rather than by a hand-tuned constant.
|
||
|
|
var compilerPool = utility.NewWorkerPool[compilerJob, struct{}](
|
||
|
|
compilerConcurrency(),
|
||
|
|
compilerConcurrency()*4,
|
||
|
|
func(_ context.Context, j compilerJob) (struct{}, error) { return struct{}{}, j() },
|
||
|
|
)
|
||
|
|
|
||
|
|
// compilerConcurrency resolves the global pool size. It defaults to the host
|
||
|
|
// vCPU count, overridable via KC_COMPILE_CONCURRENCY (mirroring the extractor
|
||
|
|
// pool's MAX_CONCURRENT_CHATS tuning knob).
|
||
|
|
func compilerConcurrency() int {
|
||
|
|
if v := os.Getenv("KC_COMPILE_CONCURRENCY"); v != "" {
|
||
|
|
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
||
|
|
return n
|
||
|
|
}
|
||
|
|
}
|
||
|
|
n := runtime.NumCPU()
|
||
|
|
if n <= 0 {
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
return n
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetCompilerConcurrency overrides the global pool size at runtime (e.g. from
|
||
|
|
// service init or tests). Mirrors SetExtractorConcurrency.
|
||
|
|
func SetCompilerConcurrency(n int) {
|
||
|
|
if n > 0 {
|
||
|
|
compilerPool.Resize(n)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// runCompilerJobs submits every job to the global pool and waits for all to
|
||
|
|
// finish, returning the first non-nil error (if any). ctx cancellation aborts
|
||
|
|
// outstanding jobs.
|
||
|
|
//
|
||
|
|
// No per-job goroutines are spun up: Submit is non-blocking until the pool's
|
||
|
|
// input buffer fills (vCPU*4 deep), so we first collect one future per job and
|
||
|
|
// then Wait on each in a second pass on the calling goroutine. This keeps the
|
||
|
|
// fan-out bounded by the shared pool's worker count while avoiding len(jobs)
|
||
|
|
// short-lived goroutines.
|
||
|
|
func runCompilerJobs(ctx context.Context, jobs []compilerJob) error {
|
||
|
|
if len(jobs) == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
futures := make([]utility.WorkerPoolFuture[compilerJob, struct{}], 0, len(jobs))
|
||
|
|
var firstErr error
|
||
|
|
for _, j := range jobs {
|
||
|
|
f, err := compilerPool.Submit(ctx, j)
|
||
|
|
if err != nil {
|
||
|
|
// Pool stopped / ctx done before we could enqueue the rest:
|
||
|
|
// remember it and stop submitting; we still await what is queued.
|
||
|
|
if firstErr == nil {
|
||
|
|
firstErr = err
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
futures = append(futures, f)
|
||
|
|
}
|
||
|
|
for _, f := range futures {
|
||
|
|
res, werr := f.Wait(ctx)
|
||
|
|
if werr != nil {
|
||
|
|
// Wait returns the context error (not a result error) when ctx wins
|
||
|
|
// the select; surface it so callers don't see a clean nil while jobs
|
||
|
|
// are incomplete.
|
||
|
|
if firstErr == nil {
|
||
|
|
firstErr = werr
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if res.Err != nil && firstErr == nil {
|
||
|
|
firstErr = res.Err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return firstErr
|
||
|
|
}
|
||
|
|
|
||
|
|
// SubmitCompilerJob runs a single job on the global pool and waits for it,
|
||
|
|
// returning its error. Used to inject bounded parallelism into lower-level
|
||
|
|
// packages (e.g. structure.LLMMergeDecider) without creating an import cycle.
|
||
|
|
func SubmitCompilerJob(ctx context.Context, fn compilerJob) error {
|
||
|
|
f, err := compilerPool.Submit(ctx, fn)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
res, werr := f.Wait(ctx)
|
||
|
|
if werr != nil {
|
||
|
|
return werr
|
||
|
|
}
|
||
|
|
return res.Err
|
||
|
|
}
|
||
|
|
|
||
|
|
// CompilerBatchSubmitter is the fan-out contract injected into lower-level
|
||
|
|
// knowledge_compiler variant packages (structure/mindmap) so every stage shares
|
||
|
|
// the one process-wide compiler pool. Implementations must submit every job to
|
||
|
|
// the shared pool, wait for all to finish, and return the first non-nil error
|
||
|
|
// (without StopWait-ing the global pool).
|
||
|
|
type CompilerBatchSubmitter func(ctx context.Context, jobs []compilerJob) error
|
||
|
|
|
||
|
|
// SubmitCompilerJobs fans out a batch of jobs on the global pool and returns the
|
||
|
|
// first error. This is the CompilerBatchSubmitter handed to variant packages.
|
||
|
|
func SubmitCompilerJobs(ctx context.Context, jobs []compilerJob) error {
|
||
|
|
return runCompilerJobs(ctx, jobs)
|
||
|
|
}
|