# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# 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.
#
"""
SQLite backend for conv/gemm profiling cache
"""
import enum
import logging
import sqlite3
from typing import Any, Dict, Tuple
import jinja2
# pylint: disable=W0613
_LOGGER = logging.getLogger(__name__)
[docs]class CacheMode(enum.Enum):
r"""Enum for cache mode
Profiling cache can be stored locally or remotely.
For LOCAL mode, the cache is stored in a SQLite database.
For REMOTE mode, the profiled results can be queried with RESTFul API.
REMOTE mode is not implemented yet.
"""
LOCAL = 1
REMOTE = 2
GEMM_INIT_TEMPLATE = jinja2.Template(
"""
CREATE TABLE IF NOT EXISTS {{dev}}_gemm_{{version}} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exec_entry VARCHAR(8192) NOT NULL,
exec_entry_sha1 VARCHAR(64) NOT NULL,
dtype_a INTEGER NOT NULL,
dtype_b INTEGER NOT NULL,
dtype_c INTEGER NOT NULL,
dtype_acc INTEGER NOT NULL,
major_a INTEGER NOT NULL,
major_b INTEGER NOT NULL,
major_c INTEGER NOT NULL,
op_type VARCHAR(512) NOT NULL,
epilogue VARCHAR(512) NOT NULL,
device VARCHAR(16) NOT NULL,
algo VARCHAR(512) NOT NULL,
workspace INTEGER DEFAULT 0,
duration FLOAT DEFAULT -1,
split_k INTEGER DEFAULT 1,
pshape VARCHAR(64) NOT NULL,
template_ver INTEGER NOT NULL DEFAULT 290,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
"""
)
GEMM_QUERY_TEMPLATE = jinja2.Template(
"""
SELECT algo, workspace, split_k
FROM {{dev}}_gemm_{{version}}
WHERE
dtype_a={{dtype_a}} AND
dtype_b={{dtype_b}} AND
dtype_c={{dtype_c}} AND
dtype_acc={{dtype_acc}} AND
major_a={{major_a}} AND
major_b={{major_b}} AND
major_c={{major_c}} AND
op_type='{{op_type}}' AND
device='{{device}}' AND
epilogue={{epilogue}} AND
pshape='{{pshape}}' AND
exec_entry_sha1='{{exec_entry_sha1}}';
"""
)
GEMM_INSERT_TEMPLATE = jinja2.Template(
"""
INSERT INTO {{dev}}_gemm_{{version}} (
exec_entry,
exec_entry_sha1,
dtype_a,
dtype_b,
dtype_c,
dtype_acc,
major_a,
major_b,
major_c,
op_type,
epilogue,
device,
algo,
workspace,
split_k,
pshape
)
VALUES (
'{{exec_entry}}',
'{{exec_entry_sha1}}',
{{dtype_a}},
{{dtype_b}},
{{dtype_c}},
{{dtype_acc}},
{{major_a}},
{{major_b}},
{{major_c}},
'{{op_type}}',
{{epilogue}},
'{{device}}',
'{{algo}}',
{{workspace}},
{{split_k}},
'{{pshape}}'
);
"""
)
CONV_INIT_TEMPLATE = jinja2.Template(
"""
CREATE TABLE IF NOT EXISTS {{dev}}_conv_{{version}} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exec_entry VARCHAR(8192) NOT NULL,
exec_entry_sha1 VARCHAR(64) NOT NULL,
dtype_a INTEGER NOT NULL,
dtype_b INTEGER NOT NULL,
dtype_c INTEGER NOT NULL,
dtype_acc INTEGER NOT NULL,
major_a INTEGER NOT NULL,
major_b INTEGER NOT NULL,
major_c INTEGER NOT NULL,
kh INTEGER NOT NULL,
kw INTEGER NOT NULL,
co INTEGER NOT NULL,
strideh INTEGER NOT NULL,
padh INTEGER NOT NULL,
dilateh INTEGER NOT NULL,
stridew INTEGER NOT NULL,
padw INTEGER NOT NULL,
dilatew INTEGER NOT NULL,
op_type VARCHAR(512) NOT NULL,
epilogue VARCHAR(512) NOT NULL,
device VARCHAR(16) NOT NULL,
algo VARCHAR(512) NOT NULL,
workspace INTEGER DEFAULT 0,
duration FLOAT DEFAULT -1,
split_k INTEGER DEFAULT 1,
template_ver INTEGER NOT NULL DEFAULT 290,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
"""
)
CONV_QUERY_TEMPLATE = jinja2.Template(
"""
SELECT algo, workspace
FROM {{dev}}_conv_{{version}}
WHERE
dtype_a={{dtype_a}} AND
dtype_b={{dtype_b}} AND
dtype_c={{dtype_c}} AND
dtype_acc={{dtype_acc}} AND
major_a={{major_a}} AND
major_b={{major_b}} AND
major_c={{major_c}} AND
kh={{kh}} AND
kw={{kw}} AND
co={{co}} AND
strideh={{strideh}} AND
padh={{padh}} AND
dilateh={{dilateh}} AND
stridew={{stridew}} AND
padw={{padw}} AND
dilatew={{dilatew}} AND
op_type='{{op_type}}' AND
device='{{device}}' AND
epilogue={{epilogue}} AND
split_k={{split_k}} AND
exec_entry_sha1='{{exec_entry_sha1}}';
"""
)
CONV_INSERT_TEMPLATE = jinja2.Template(
"""
INSERT INTO {{dev}}_conv_{{version}} (
exec_entry,
exec_entry_sha1,
dtype_a,
dtype_b,
dtype_c,
dtype_acc,
major_a,
major_b,
major_c,
kh,
kw,
co,
strideh,
padh,
dilateh,
stridew,
padw,
dilatew,
op_type,
epilogue,
device,
algo,
workspace,
split_k
)
VALUES (
'{{exec_entry}}',
'{{exec_entry_sha1}}',
{{dtype_a}},
{{dtype_b}},
{{dtype_c}},
{{dtype_acc}},
{{major_a}},
{{major_b}},
{{major_c}},
{{kh}},
{{kw}},
{{co}},
{{strideh}},
{{padh}},
{{dilateh}},
{{stridew}},
{{padw}},
{{dilatew}},
'{{op_type}}',
{{epilogue}},
'{{device}}',
'{{algo}}',
{{workspace}},
{{split_k}}
);
"""
)
CONV3D_INIT_TEMPLATE = jinja2.Template(
"""
CREATE TABLE IF NOT EXISTS {{dev}}_conv3d_{{version}} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exec_entry VARCHAR(8192) NOT NULL,
exec_entry_sha1 VARCHAR(64) NOT NULL,
dtype_a INTEGER NOT NULL,
dtype_b INTEGER NOT NULL,
dtype_c INTEGER NOT NULL,
dtype_acc INTEGER NOT NULL,
major_a INTEGER NOT NULL,
major_b INTEGER NOT NULL,
major_c INTEGER NOT NULL,
kd INTEGER NOT NULL,
kh INTEGER NOT NULL,
kw INTEGER NOT NULL,
co INTEGER NOT NULL,
stride_d INTEGER NOT NULL,
stride_h INTEGER NOT NULL,
stride_w INTEGER NOT NULL,
pad_d INTEGER NOT NULL,
pad_h INTEGER NOT NULL,
pad_w INTEGER NOT NULL,
dilate_d INTEGER NOT NULL,
dilate_h INTEGER NOT NULL,
dilate_w INTEGER NOT NULL,
op_type VARCHAR(512) NOT NULL,
epilogue VARCHAR(512) NOT NULL,
device VARCHAR(16) NOT NULL,
algo VARCHAR(512) NOT NULL,
workspace INTEGER DEFAULT 0,
duration FLOAT DEFAULT -1,
split_k INTEGER DEFAULT 1,
template_ver INTEGER NOT NULL DEFAULT 290,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
"""
)
CONV3D_QUERY_TEMPLATE = jinja2.Template(
"""
SELECT algo, workspace
FROM {{dev}}_conv3d_{{version}}
WHERE
dtype_a={{dtype_a}} AND
dtype_b={{dtype_b}} AND
dtype_c={{dtype_c}} AND
dtype_acc={{dtype_acc}} AND
major_a={{major_a}} AND
major_b={{major_b}} AND
major_c={{major_c}} AND
kd={{kd}} AND
kh={{kh}} AND
kw={{kw}} AND
co={{co}} AND
stride_d={{stride_d}} AND
stride_h={{stride_h}} AND
stride_w={{stride_w}} AND
pad_d={{pad_d}} AND
pad_h={{pad_h}} AND
pad_w={{pad_w}} AND
dilate_d={{dilate_d}} AND
dilate_h={{dilate_h}} AND
dilate_w={{dilate_w}} AND
op_type='{{op_type}}' AND
device='{{device}}' AND
epilogue={{epilogue}} AND
split_k={{split_k}} AND
exec_entry_sha1='{{exec_entry_sha1}}';
"""
)
CONV3D_INSERT_TEMPLATE = jinja2.Template(
"""
INSERT INTO {{dev}}_conv3d_{{version}} (
exec_entry,
exec_entry_sha1,
dtype_a,
dtype_b,
dtype_c,
dtype_acc,
major_a,
major_b,
major_c,
kd,
kh,
kw,
co,
stride_d,
stride_h,
stride_w,
pad_d,
pad_h,
pad_w,
dilate_d,
dilate_h,
dilate_w,
op_type,
epilogue,
device,
algo,
workspace,
split_k
)
VALUES (
'{{exec_entry}}',
'{{exec_entry_sha1}}',
{{dtype_a}},
{{dtype_b}},
{{dtype_c}},
{{dtype_acc}},
{{major_a}},
{{major_b}},
{{major_c}},
{{kd}},
{{kh}},
{{kw}},
{{co}},
{{stride_d}},
{{stride_h}},
{{stride_w}},
{{pad_d}},
{{pad_h}},
{{pad_w}},
{{dilate_d}},
{{dilate_h}},
{{dilate_w}},
'{{op_type}}',
{{epilogue}},
'{{device}}',
'{{algo}}',
{{workspace}},
{{split_k}}
);
"""
)
NORM_INIT_TEMPLATE = jinja2.Template(
"""
CREATE TABLE IF NOT EXISTS {{dev}}_normalization (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exec_entry VARCHAR(8192) NOT NULL,
exec_entry_sha1 VARCHAR(64) NOT NULL,
dtype_in INTEGER NOT NULL,
dtype_acc INTEGER NOT NULL,
dtype_out INTEGER NOT NULL,
rank INTEGER NOT NULL,
op_type VARCHAR(512) NOT NULL,
device VARCHAR(16) NOT NULL,
algo VARCHAR(512) NOT NULL,
workspace INTEGER DEFAULT 0,
duration FLOAT DEFAULT -1,
template_ver INTEGER NOT NULL DEFAULT 290,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
"""
)
NORM_QUERY_TEMPLATE = jinja2.Template(
"""
SELECT algo, workspace
FROM {{dev}}_normalization
WHERE
dtype_in={{dtype_in}} AND
dtype_out={{dtype_out}} AND
dtype_acc={{dtype_acc}} AND
rank={{rank}} AND
op_type='{{op_type}}' AND
device='{{device}}' AND
exec_entry_sha1='{{exec_entry_sha1}}';
"""
)
NORM_INSERT_TEMPLATE = jinja2.Template(
"""
INSERT INTO {{dev}}_normalization (
exec_entry,
exec_entry_sha1,
dtype_in,
dtype_out,
dtype_acc,
rank,
op_type,
device,
algo,
workspace
)
VALUES (
'{{exec_entry}}',
'{{exec_entry_sha1}}',
{{dtype_in}},
{{dtype_out}},
{{dtype_acc}},
{{rank}},
'{{op_type}}',
'{{device}}',
'{{algo}}',
{{workspace}}
);
"""
)
CHECK_TABLE_EXISTENCE_TEMPLATE = jinja2.Template(
"""
SELECT name FROM sqlite_master WHERE type='table' AND name='{{table_name}}';
"""
)
QUERY_ALL_TABLES_TEMPLATE = jinja2.Template(
"""
SELECT name FROM sqlite_master WHERE type='table';
"""
)
__AIT_CACHE_VERSION__ = 3
def ait_cache_version() -> int:
return __AIT_CACHE_VERSION__
[docs]class ProfileCacheDB:
r"""Local SQLite profile cache database."""
def __init__(
self, target: str, path: str = None, uri: str = None, port: str = None
):
r"""=
Parameters
----------
target : str
target device name. CUDA or ROCM
path : str, optional
path to the database file. If not specified, a temporary file is created.
uri : str, optional
uri to the RESFul API (Not implemented yet)
port : str, optional
port to the RESFul API (Not implemented yet)
"""
self._target = target
self._mode = CacheMode.LOCAL
self._db_commit_flag = False
# Some design rationales:
# * All tables share the version number, because we are exposing the
# the cache version. Using a single version number seems to make it
# more clean. One caveat is that we are going to re-create all tables
# even if some of them are not involved with the breaking changes.
# It seems to be fine as we expect the frequency of cache version
# updated to be quite low.
# * We only keep a single table (i.e. version) for each category (
# gemm, conv and norm) to simplify how we handle breaking changes
# and rollbacks caused by failures in the updated version.
# For example, if we keep multiple versions (i.e. tables) for gemm,
# we would have to consider how we were going to maintain those versions.
# We could choose the old working version upon rollback, but we might
# leave some content from the failing version in the db. How are we
# going to update the db if we update the version again, and so on.
# TODO: add similar version control for norm
self._gemm_cache_version = ait_cache_version()
self._conv_cache_version = ait_cache_version()
self._conv3d_cache_version = ait_cache_version()
if uri is not None:
self._mode = CacheMode.REMOTE
if self._mode == CacheMode.LOCAL:
assert path is not None
self._con = sqlite3.connect(path)
self._cur = self._con.cursor()
self._init_db()
else:
raise NotImplementedError
def _init_db(self):
"""Creates table in cache."""
self._create_gemm_table()
self._create_conv_table()
self._create_conv3d_table()
self._create_norm_table()
@property
def gemm_cache_version(self) -> int:
return self._gemm_cache_version
@property
def conv_cache_version(self) -> int:
return self._conv_cache_version
@property
def conv3d_cache_version(self) -> int:
return self._conv3d_cache_version
def _create_gemm_table(self):
"""Creates gemm table."""
version = self.gemm_cache_version
if not self._table_exists("gemm", version):
_LOGGER.info(
"Temporarily keeping the old gemm cache versions if exist",
)
# FIXME: will delete unmatched version once we get into production
# self._delete_existing_table("gemm")
_LOGGER.info(
f"Creating a new gemm table with {version=}",
)
sql = GEMM_INIT_TEMPLATE.render(
dev=self._target,
version=version,
)
self._cur.execute(sql)
self._con.commit()
def _create_conv_table(self):
"""Creates conv table."""
version = self.conv_cache_version
if not self._table_exists("conv", version):
_LOGGER.info(
"Temporarily keeping the old conv cache versions if exist",
)
# FIXME: will delete unmatched version once we get into production
# self._delete_existing_table("conv")
_LOGGER.info(
f"Creating a new conv table with {version=}",
)
sql = CONV_INIT_TEMPLATE.render(
dev=self._target,
version=version,
)
self._cur.execute(sql)
self._con.commit()
def _create_conv3d_table(self):
"""Creates conv3d table."""
version = self.conv3d_cache_version
if not self._table_exists("conv3d", version):
_LOGGER.info(
"Temporarily keeping the old conv3d cache versions if exist",
)
# FIXME: will delete unmatched version once we get into production
# self._delete_existing_table("conv3d")
_LOGGER.info(
f"Creating a new conv3d table with {version=}",
)
sql = CONV3D_INIT_TEMPLATE.render(
dev=self._target,
version=version,
)
self._cur.execute(sql)
self._con.commit()
def _create_norm_table(self):
"""Creates conv table."""
sql = NORM_INIT_TEMPLATE.render(dev=self._target)
self._cur.execute(sql)
self._con.commit()
def _table_exists(self, table_kind, cache_version):
"""Check if the table of given kind and cache version exists."""
table_name = f"{self._target}_{table_kind}_{cache_version}"
sql = CHECK_TABLE_EXISTENCE_TEMPLATE.render(table_name=table_name)
self._cur.execute(sql)
tables = self._cur.fetchall()
if tables:
_LOGGER.info(
f"{table_name=} exists in the db",
)
return True
else:
_LOGGER.info(
f"{table_name=} does not exist in the db, possible version mismatch!",
)
return False
def _delete_existing_table(self, table_kind):
"""Delete an existing table in the db"""
sql = QUERY_ALL_TABLES_TEMPLATE.render()
self._cur.execute(sql)
all_tables = self._cur.fetchall()
if len(all_tables) == 0:
_LOGGER.info("deleting table: skip empty table")
return
target_tables = [
table[0]
for table in all_tables
if table[0].startswith(f"{self._target}_{table_kind}")
]
assert len(target_tables) != 0, f"no {table_kind} table exists"
# To simplify the logic, we only keep a single table for each kind
assert (
len(target_tables) == 1
), f"expected only one {table_kind} table but got {target_tables=}"
_LOGGER.info(f"deleting table {target_tables[0]=}")
self._cur.execute(f"DROP TABLE {target_tables[0]}")
def _query(self, sql: str) -> Tuple[str, int]:
"""a function to query op from cache
Parameters
----------
sql : str
sql statement for query
Returns
-------
Tuple
profiling results
"""
if self._mode == CacheMode.LOCAL:
if self._db_commit_flag:
self._con.commit()
self._db_commit_flag = False
self._cur.execute(sql)
out = self._cur.fetchall()
if len(out) == 0:
return None
return out[0]
raise NotImplementedError
[docs] def query_gemm(self, args: Dict[str, Any]) -> Tuple[str, int]:
"""a function to query gemm op epilogue from cache
Parameters
----------
args : Dict
gemm query entry
Returns
-------
Tuple
profiling results
"""
sql = GEMM_QUERY_TEMPLATE.render(
dev=self._target,
version=self.gemm_cache_version,
**args,
)
return self._query(sql)
[docs] def query_conv(self, args: Dict[str, Any]) -> Tuple[str, int]:
"""a function to query conv op epilogue from cache,
here we use the same sql table for conv and gemm
Parameters
----------
args : Dict
Conv query entry
Returns
-------
Tuple
profiling results
"""
sql = CONV_QUERY_TEMPLATE.render(
dev=self._target,
version=self.conv_cache_version,
**args,
)
return self._query(sql)
[docs] def query_conv3d(self, args: Dict[str, Any]) -> Tuple[str, int]:
"""a function to query conv op epilogue from cache,
here we use the same sql table for conv and gemm
Parameters
----------
args : Dict
Conv3d query entry
Returns
-------
Tuple
profiling results
"""
sql = CONV3D_QUERY_TEMPLATE.render(
dev=self._target,
version=self.conv3d_cache_version,
**args,
)
return self._query(sql)
[docs] def query_normalization(self, args: Dict[str, Any]) -> Tuple[str, int]:
"""a function to query normalization op epilogue from cache
Parameters
----------
args : Dict
Conv query entry
Returns
-------
Tuple
profiling results
"""
sql = NORM_QUERY_TEMPLATE.render(dev=self._target, **args)
return self._query(sql)
def _insert(self, query_sql: str, insert_sql: str) -> None:
"""a function to insert op into cache
Parameters
----------
query_sql : str
cache query sql
insert_sql: str
cache insert sql
"""
if self._mode == CacheMode.LOCAL:
self._cur.execute(query_sql)
out = self._cur.fetchall()
if len(out) == 0:
self._cur.execute(insert_sql)
self._db_commit_flag = True
else:
_LOGGER.info("Ignore repeat profile_record: " + query_sql)
[docs] def insert_gemm(self, args: Dict[str, Any]) -> None:
"""a function to insert gemm op epilogue into cache
Parameters
----------
args : Dict
Gemm Record Entry
"""
query_sql = GEMM_QUERY_TEMPLATE.render(
dev=self._target,
version=self.gemm_cache_version,
dtype_a=args["dtype_a"],
dtype_b=args["dtype_b"],
dtype_c=args["dtype_c"],
dtype_acc=args["dtype_acc"],
major_a=args["major_a"],
major_b=args["major_b"],
major_c=args["major_c"],
op_type=args["op_type"],
device=args["device"],
epilogue=args["epilogue"],
split_k=args["split_k"],
pshape=args["pshape"],
exec_entry_sha1=args["exec_entry_sha1"],
)
insert_sql = GEMM_INSERT_TEMPLATE.render(
dev=self._target,
version=self.gemm_cache_version,
**args,
)
self._insert(query_sql, insert_sql)
[docs] def insert_conv(self, args: Dict[str, Any]) -> None:
"""a function to insert conv op epilogue into cache,
here we use the same sql table for conv and gemm
Parameters
----------
args : Dict
Conv Record Entry
"""
query_sql = CONV_QUERY_TEMPLATE.render(
dev=self._target,
version=self.conv_cache_version,
dtype_a=args["dtype_a"],
dtype_b=args["dtype_b"],
dtype_c=args["dtype_c"],
dtype_acc=args["dtype_acc"],
major_a=args["major_a"],
major_b=args["major_b"],
major_c=args["major_c"],
kh=args["kh"],
kw=args["kw"],
co=args["co"],
strideh=args["strideh"],
padh=args["padh"],
dilateh=args["dilateh"],
stridew=args["stridew"],
padw=args["padw"],
dilatew=args["dilatew"],
op_type=args["op_type"],
device=args["device"],
epilogue=args["epilogue"],
split_k=args["split_k"],
exec_entry_sha1=args["exec_entry_sha1"],
)
insert_sql = CONV_INSERT_TEMPLATE.render(
dev=self._target,
version=self.conv_cache_version,
**args,
)
self._insert(query_sql, insert_sql)
[docs] def insert_conv3d(self, args: Dict[str, Any]) -> None:
"""a function to insert conv op epilogue into cache,
here we use the same sql table for conv and gemm
Parameters
----------
args : Dict
Conv Record Entry
"""
query_sql = CONV3D_QUERY_TEMPLATE.render(
dev=self._target,
version=self.conv3d_cache_version,
dtype_a=args["dtype_a"],
dtype_b=args["dtype_b"],
dtype_c=args["dtype_c"],
dtype_acc=args["dtype_acc"],
major_a=args["major_a"],
major_b=args["major_b"],
major_c=args["major_c"],
kd=args["kd"],
kh=args["kh"],
kw=args["kw"],
co=args["co"],
stride_d=args["stride_d"],
stride_h=args["stride_h"],
stride_w=args["stride_w"],
pad_d=args["pad_d"],
pad_h=args["pad_h"],
pad_w=args["pad_w"],
dilate_d=args["dilate_d"],
dilate_h=args["dilate_h"],
dilate_w=args["dilate_w"],
op_type=args["op_type"],
device=args["device"],
epilogue=args["epilogue"],
split_k=args["split_k"],
exec_entry_sha1=args["exec_entry_sha1"],
)
insert_sql = CONV3D_INSERT_TEMPLATE.render(
dev=self._target,
version=self.conv3d_cache_version,
**args,
)
self._insert(query_sql, insert_sql)
[docs] def insert_normalization(self, args: Dict[str, Any]) -> None:
"""a function to insert conv op epilogue into cache,
here we use the same sql table for conv and gemm
Parameters
----------
args : Dict
Conv Record Entry
"""
query_sql = NORM_QUERY_TEMPLATE.render(
dev=self._target,
dtype_in=args["dtype_in"],
dtype_acc=args["dtype_acc"],
dtype_out=args["dtype_out"],
rank=args["rank"],
op_type=args["op_type"],
device=args["device"],
exec_entry_sha1=args["exec_entry_sha1"],
)
insert_sql = NORM_INSERT_TEMPLATE.render(dev=self._target, **args)
self._insert(query_sql, insert_sql)
def __del__(self):
self._con.commit()
self._con.close()