Разработка минимального FastAPI-сервиса
Уроки курсаРазработка минимального FastAPI-сервиса
5. Типичная ошибка: загрузка модели внутри функции-обработчика
Это одна из тех ошибок, которую делают почти все, кто впервые пишет ML-сервис. Выглядит логично: клиент прислал запрос — открыл файл, загрузил модель, получил предсказание. На практике это катастрофа по производительности. Антипример:
@app.post("/predict", response_model=PredictResponse)
def predict(data: InputData):
# ❌ Так делать нельзя
with open('model.pkl', 'rb') as f:
clf = pickle.load(f)
X = np.array([data.Store, data.DayOfWeek, data.Promo, data.SchoolHoliday]).reshape(1, -1)
return PredictResponse(predicted_sales=float(clf.predict(X)[0]))
Что здесь происходит при каждом запросе: открывается файл на диске, Python читает и десериализует весь объект модели заново, только потом вызывается predict(). Если модель занимает 50 МБ (градиентный бустинг на данных Rossmann вполне может весить столько), то при 100 одновременных запросах в секунду сервис будет заниматься исключительно чтением диска, а не предсказаниями. Правильный вариант — загрузить модель один раз в lifespan и держать её в памяти:
ml_model = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
with open('model.pkl', 'rb') as f:
ml_model['clf'] = pickle.load(f)
yield
ml_model.clear()
app = FastAPI(lifespan=lifespan)
@app.post("/predict", response_model=PredictResponse)
def predict(data: InputData):
# ✅ Модель уже в памяти — только вызов predict()
X = np.array([data.Store, data.DayOfWeek, data.Promo, data.SchoolHoliday]).reshape(1, -1)
return PredictResponse(predicted_sales=float(ml_model['clf'].predict(X)[0]))
Теперь pickle.load() выполняется ровно один раз при старте uvicorn. Все последующие запросы обращаются к уже готовому объекту ml_model['clf'] в оперативной памяти — это на несколько порядков быстрее дискового I/O. Почему именно словарь, а не просто global clf? В Python переназначение глобальной переменной внутри async-функции требует явного объявления global clf в каждой функции, которая её меняет, иначе Python создаст локальную переменную с тем же именем. Словарь ml_model — это изменяемый объект, ссылка на который остаётся глобальной; мутировать его ключи можно без global-декларации, и это работает предсказуемо.
