I am trying to deploy a flask app that uses the argparse module to add arguments for a pytorch model that dictates particular options for how the model should run.
Everything runs fine locally however when trying to run my flask app with gunicorn app:app on a ubuntu ec2 instance it looks like in addition gunicorn is trying to parse the arguments thrown in by argparse from the flask app.
If I don't use gunicorn, I run my application as follows:
Sudo Python app.py
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
But if I try to run with gunicorn like this:
Sudo gunicorn app:app
I get the following error :
usage: gunicorn [-h] [--name NAME] [--gpu_ids GPU_IDS]
[--checkpoints_dir CHECKPOINTS_DIR] [--model MODEL]
[--norm NORM] [--use_dropout] [--data_type {8,16,32}]
[--verbose] [--fp16] [--local_rank LOCAL_RANK]
[--batchSize BATCHSIZE] [--loadSize LOADSIZE]
[--fineSize FINESIZE] [--label_nc LABEL_NC]
[--input_nc INPUT_NC] [--output_nc OUTPUT_NC]
[--resize_or_crop RESIZE_OR_CROP] [--serial_batches]
[--no_flip] [--nThreads NTHREADS]
[--max_dataset_size MAX_DATASET_SIZE]
[--display_winsize DISPLAY_WINSIZE] [--tf_log] [--netG NETG]
[--ngf NGF] [--n_downsample_global N_DOWNSAMPLE_GLOBAL]
[--n_blocks_global N_BLOCKS_GLOBAL]
[--n_blocks_local N_BLOCKS_LOCAL]
[--n_local_enhancers N_LOCAL_ENHANCERS]
[--niter_fix_global NITER_FIX_GLOBAL] [--no_instance]
[--instance_feat] [--label_feat] [--feat_num FEAT_NUM]
[--load_features] [--n_downsample_E N_DOWNSAMPLE_E]
[--nef NEF] [--n_clusters N_CLUSTERS] [--ntest NTEST]
[--results_dir RESULTS_DIR] [--aspect_ratio ASPECT_RATIO]
[--phase PHASE] [--which_epoch WHICH_EPOCH]
[--how_many HOW_MANY] [--cluster_path CLUSTER_PATH]
[--use_encoded_image] [--export_onnx EXPORT_ONNX]
[--engine ENGINE] [--onnx ONNX]
gunicorn: error: unrecognized arguments: app:ap
Here is the flask app code:
opt = BaseOptions().parse(save=False)
opt.nThreads = 1 # test code only supports nThreads = 1
opt.batchSize = 1 # test code only supports batchSize = 1
opt.serial_batches = True # no shuffle
opt.no_flip = True # no flip
# Config
app = Flask(__name__)
CORS(app)
app.config['UPLOAD_DIR'] = os.path.join(os.getcwd(), 'upload')
app.config['TEST_A'] = os.path.join(os.getcwd(), 'test_A')
app.config['RESULT_DIR'] = os.path.join(os.getcwd(), 'results')
app.config['ALLOWED_EXTENSIONS'] = set(['jpg', 'jpeg', 'png'])
# Setup
if not os.path.exists(app.config['UPLOAD_DIR']):
os.mkdir(app.config['UPLOAD_DIR'])
if not os.path.exists(app.config['RESULT_DIR']):
os.mkdir(app.config['RESULT_DIR'])
# Helpers
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']
def error(msg):
return jsonify({'error': msg})
# Routers
@app.route('/')
def pong():
return 'Pong', {'Content-Type': 'text-plain; charset=utf-8'}
@app.route('/gen', methods=['POST'])
def gen():
print("request.files", request.files)
if 'file' not in request.files:
return error('file form-data not existed'), 412
image = request.files['file']
print("image", image)
if not allowed_file(image.filename):
return error('Only supported %s' % app.config['ALLOWED_EXTENSIONS']), 415
# Submit taylor.jpg ---> save image to upload/12345678/taylor.jpg (upload/timestamp/imagename.ext)
t = int(time.time())
image_dir = os.path.join(app.config['UPLOAD_DIR'], str(t))
print("IMAGE DIR HELLO")
print(image_dir)
os.mkdir(image_dir)
test_A_Dir = os.path.join(image_dir, 'test_A')
image_path = os.path.join(test_A_Dir, image.filename)
print("image_dir", test_A_Dir)
print("image_path", image_path)
os.mkdir(test_A_Dir)
new_image_path = os.path.join(test_A_Dir, image.filename)
image.save(new_image_path)
opt.dataroot = test_A_Dir
data_loader = CreateDataLoader(opt)
print(data_loader)
dataset = data_loader.load_data()
print(dataset)
# test
if not opt.engine and not opt.onnx:
model = create_model(opt)
if opt.data_type == 16:
model.half()
elif opt.data_type == 8:
model.type(torch.uint8)
if opt.verbose:
else:
from run_engine import run_trt_engine, run_onnx
for i, data in enumerate(dataset):
if i >= opt.how_many:
break
if opt.data_type == 16:
data['label'] = data['label'].half()
data['inst'] = data['inst'].half()
elif opt.data_type == 8:
data['label'] = data['label'].uint8()
data['inst'] = data['inst'].uint8()
if opt.export_onnx:
print ("Exporting to ONNX: ", opt.export_onnx)
assert opt.export_onnx.endswith("onnx"), "Export model file should end with .onnx"
torch.onnx.export(model, [data['label'], data['inst']],
opt.export_onnx, verbose=True)
exit(0)
minibatch = 1
if opt.engine:
generated = run_trt_engine(opt.engine, minibatch, [data['label'], data['inst']])
elif opt.onnx:
generated = run_onnx(opt.onnx, opt.data_type, minibatch, [data['label'], data['inst']])
else:
generated = model.inference(data['label'], data['inst'], data['image'])
t2 = int(time.time())
# Convert image to numpy array
fake =util.tensor2im(generated.data[0])
result_dir = os.path.join(app.config['RESULT_DIR'], str(t2))
result_path = os.path.join(result_dir, image.filename)
os.mkdir(result_dir)
util.save_image(fake, result_path)
return send_file(result_path,mimetype='image/jpeg',
as_attachment=True,)
if __name__ == '__main__':
app.run()
Here is the file parsing the arguments :
class BaseOptions():
def __init__(self):
self.parser = argparse.ArgumentParser()
self.initialized = False
def initialize(self):
# experiment specifics
self.parser.add_argument('--name', type=str, default='caf_pix2pix', help='name of the experiment. It decides where to store samples and models')
self.parser.add_argument('--gpu_ids', type=str, default='-1', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
self.parser.add_argument('--checkpoints_dir', type=str, default='./trained', help='models are saved here')
self.parser.add_argument('--model', type=str, default='pix2pixHD', help='which model to use')
self.parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization')
self.parser.add_argument('--use_dropout', action='store_true', help='use dropout for the generator')
self.parser.add_argument('--data_type', default=32, type=int, choices=[8, 16, 32], help="Supported data type i.e. 8, 16, 32 bit")
self.parser.add_argument('--verbose', action='store_true', default=False, help='toggles verbose')
self.parser.add_argument('--fp16', action='store_true', default=False, help='train with AMP')
self.parser.add_argument('--local_rank', type=int, default=0, help='local rank for distributed training')
# input/output sizes
self.parser.add_argument('--batchSize', type=int, default=1, help='input batch size')
self.parser.add_argument('--loadSize', type=int, default=1024, help='scale images to this size')
self.parser.add_argument('--fineSize', type=int, default=512, help='then crop to this size')
self.parser.add_argument('--label_nc', type=int, default= 0 , help='# of input label channels')
self.parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels')
self.parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels')
# for setting inputs
# self.parser.add_argument('--dataroot', type=str, default='./datasets/w/')
self.parser.add_argument('--resize_or_crop', type=str, default='scale_width', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]')
self.parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly')
self.parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data argumentation')
self.parser.add_argument('--nThreads', default=2, type=int, help='# threads for loading data')
self.parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.')
# for displays
self.parser.add_argument('--display_winsize', type=int, default=512, help='display window size')
self.parser.add_argument('--tf_log', action='store_true', help='if specified, use tensorboard logging. Requires tensorflow installed')
# for generator
self.parser.add_argument('--netG', type=str, default='global', help='selects model to use for netG')
self.parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer')
self.parser.add_argument('--n_downsample_global', type=int, default=4, help='number of downsampling layers in netG')
self.parser.add_argument('--n_blocks_global', type=int, default=9, help='number of residual blocks in the global generator network')
self.parser.add_argument('--n_blocks_local', type=int, default=3, help='number of residual blocks in the local enhancer network')
self.parser.add_argument('--n_local_enhancers', type=int, default=1, help='number of local enhancers to use')
self.parser.add_argument('--niter_fix_global', type=int, default=0, help='number of epochs that we only train the outmost local enhancer')
# for instance-wise features
self.parser.add_argument('--no_instance', action='store_false', help='if specified, do *not* add instance map as input')
self.parser.add_argument('--instance_feat', action='store_true', help='if specified, add encoded instance features as input')
self.parser.add_argument('--label_feat', action='store_true', help='if specified, add encoded label features as input')
self.parser.add_argument('--feat_num', type=int, default=3, help='vector length for encoded features')
self.parser.add_argument('--load_features', action='store_true', help='if specified, load precomputed feature maps')
self.parser.add_argument('--n_downsample_E', type=int, default=4, help='# of downsampling layers in encoder')
self.parser.add_argument('--nef', type=int, default=16, help='# of encoder filters in the first conv layer')
self.parser.add_argument('--n_clusters', type=int, default=10, help='number of clusters for features')
self.initialized = True
def parse(self, save=True):
if not self.initialized:
self.initialize()
self.opt = self.parser.parse_args()
self.opt.isTrain = self.isTrain # train or test
str_ids = self.opt.gpu_ids.split(',')
self.opt.gpu_ids = []
for str_id in str_ids:
id = int(str_id)
if id >= 0:
self.opt.gpu_ids.append(id)
# set gpu ids
if len(self.opt.gpu_ids) > 0:
torch.cuda.set_device(self.opt.gpu_ids[0])
args = vars(self.opt)
print('------------ Options -------------')
for k, v in sorted(args.items()):
print('%s: %s' % (str(k), str(v)))
print('-------------- End ----------------')
# save to the disk
expr_dir = os.path.join(self.opt.checkpoints_dir, self.opt.name)
util.mkdirs(expr_dir)
if save and not self.opt.continue_train:
file_name = os.path.join(expr_dir, 'opt.txt')
with open(file_name, 'wt') as opt_file:
opt_file.write('------------ Options -------------\n')
for k, v in sorted(args.items()):
opt_file.write('%s: %s\n' % (str(k), str(v)))
opt_file.write('-------------- End ----------------\n')
return self.opt