codefinetuner 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codefinetuner/__init__.py +0 -0
- codefinetuner/convert/__init__.py +0 -0
- codefinetuner/convert/config.py +107 -0
- codefinetuner/convert/convert_hf_to_gguf.py +12130 -0
- codefinetuner/convert/convert_hf_to_gguf.version +1 -0
- codefinetuner/convert/run.py +82 -0
- codefinetuner/evaluate/__init__.py +0 -0
- codefinetuner/evaluate/analyze.py +198 -0
- codefinetuner/evaluate/benchmark.py +95 -0
- codefinetuner/evaluate/codebleu_shim.py +37 -0
- codefinetuner/evaluate/config.py +186 -0
- codefinetuner/evaluate/evaluate.py +63 -0
- codefinetuner/evaluate/generate.py +145 -0
- codefinetuner/evaluate/metrics.py +160 -0
- codefinetuner/evaluate/run.py +125 -0
- codefinetuner/finetune/__init__.py +0 -0
- codefinetuner/finetune/config.py +186 -0
- codefinetuner/finetune/model.py +59 -0
- codefinetuner/finetune/run.py +136 -0
- codefinetuner/finetune/train.py +204 -0
- codefinetuner/pipeline.py +152 -0
- codefinetuner/preprocess/__init__.py +0 -0
- codefinetuner/preprocess/config.py +160 -0
- codefinetuner/preprocess/extract.py +164 -0
- codefinetuner/preprocess/process.py +180 -0
- codefinetuner/preprocess/run.py +133 -0
- codefinetuner/preprocess/tree_sitter_definitions.json +189 -0
- codefinetuner-0.1.0.dist-info/METADATA +294 -0
- codefinetuner-0.1.0.dist-info/RECORD +32 -0
- codefinetuner-0.1.0.dist-info/WHEEL +4 -0
- codefinetuner-0.1.0.dist-info/entry_points.txt +2 -0
- codefinetuner-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Iterator, Tuple
|
|
5
|
+
|
|
6
|
+
import tree_sitter as ts
|
|
7
|
+
from tree_sitter_language_pack import get_parser
|
|
8
|
+
|
|
9
|
+
from .config import Config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_custom_tree_sitter_parser(tree_sitter_lib_path: Path, data_language: str) -> ts.Parser:
|
|
16
|
+
if not tree_sitter_lib_path.exists():
|
|
17
|
+
raise FileNotFoundError(f"Library not found: {tree_sitter_lib_path}")
|
|
18
|
+
try:
|
|
19
|
+
lib = ctypes.CDLL(str(tree_sitter_lib_path)) # Load C Dynamic Link Library, makes all the public C functions inside the .dylib file available to be called from the Python script.
|
|
20
|
+
entry_point_func_name = f"tree_sitter_{data_language}"
|
|
21
|
+
lang_func = getattr(lib, entry_point_func_name) # Retrieves the entry point function of the loaded lib
|
|
22
|
+
lang_func.restype = ctypes.c_void_p # Tells ctypes that this function returns a C-style pointer (void *)
|
|
23
|
+
grammar_rules = ts.Language(lang_func()) # Call lang_func() function and wrap the returned raw C pointer into a tree-sitter Language object
|
|
24
|
+
return ts.Parser(grammar_rules)
|
|
25
|
+
except (AttributeError, OSError) as e:
|
|
26
|
+
raise RuntimeError(f"Failed to load custom tree sitter language parser: {e}") from e
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_tree_sitter_language_pack_parser(data_language: str) -> ts.Parser:
|
|
30
|
+
return get_parser(data_language)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def auto_create_split_paths(config: Config) -> Tuple[list[Path], list[Path], list[Path]]:
|
|
34
|
+
all_file_paths = []
|
|
35
|
+
for filepath in config.raw_data_path.rglob("*"):
|
|
36
|
+
if not filepath.is_file():
|
|
37
|
+
continue
|
|
38
|
+
if filepath.suffix.lower() not in config.data_extensions:
|
|
39
|
+
continue
|
|
40
|
+
all_file_paths.append(filepath)
|
|
41
|
+
|
|
42
|
+
config.rng.shuffle(all_file_paths)
|
|
43
|
+
|
|
44
|
+
num_files = len(all_file_paths)
|
|
45
|
+
if num_files == 0:
|
|
46
|
+
logger.warning(f"No source files found under {config.raw_data_path} with extensions {config.data_extensions}")
|
|
47
|
+
return [], [], []
|
|
48
|
+
|
|
49
|
+
train_end = int(num_files * config.train_ratio)
|
|
50
|
+
eval_end = int(num_files * (config.train_ratio + config.eval_ratio))
|
|
51
|
+
|
|
52
|
+
train_file_paths = all_file_paths[:train_end]
|
|
53
|
+
eval_file_paths = all_file_paths[train_end:eval_end]
|
|
54
|
+
test_file_paths = all_file_paths[eval_end:]
|
|
55
|
+
|
|
56
|
+
logger.info(f"Split {num_files} files into {len(train_file_paths)} train, {len(eval_file_paths)} eval, and {len(test_file_paths)} test files.")
|
|
57
|
+
|
|
58
|
+
return train_file_paths, eval_file_paths, test_file_paths
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _extract_code_blocks_rec(config: Config, node: ts.Node, source_code_utf8: bytes, max_depth: int) -> list[Tuple[bytes, ts.Node]]:
|
|
62
|
+
"""
|
|
63
|
+
Recursively extract code blocks (e.g. functions) that are used for FIM example generation later
|
|
64
|
+
"""
|
|
65
|
+
if max_depth <= 0:
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
code_blocks = []
|
|
69
|
+
if node.type in config.tree_sitter_block_types:
|
|
70
|
+
code_utf8 = source_code_utf8[node.start_byte:node.end_byte]
|
|
71
|
+
code_blocks.append((code_utf8, node))
|
|
72
|
+
|
|
73
|
+
for child in node.children:
|
|
74
|
+
child_blocks = _extract_code_blocks_rec(config, child, source_code_utf8, max_depth-1)
|
|
75
|
+
code_blocks.extend(child_blocks)
|
|
76
|
+
return code_blocks
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_code_blocks_from_paths(config: Config, file_paths: list[Path]) -> Iterator[Tuple[bytes, ts.Node]]:
|
|
80
|
+
"""
|
|
81
|
+
Generator function yielding code blocks from files one-by-one.
|
|
82
|
+
|
|
83
|
+
How generators work:
|
|
84
|
+
1. Call function: get iterator object (doesn't run code yet)
|
|
85
|
+
2. next(iterator): processes 1 file, yields first block, then pauses
|
|
86
|
+
3. next(iterator): resumes, yields next block, then pauses
|
|
87
|
+
4. Repeat until end of code blocks, then the generator is exhausted. Generators can only be used once.
|
|
88
|
+
"""
|
|
89
|
+
for path in file_paths:
|
|
90
|
+
if not path.is_file():
|
|
91
|
+
continue
|
|
92
|
+
try:
|
|
93
|
+
source_code_unicode = path.read_text(encoding='utf8')
|
|
94
|
+
except UnicodeDecodeError:
|
|
95
|
+
logger.warning(f"Skipping file '{path}': Not a valid UTF-8 file.")
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
source_code_utf8 = source_code_unicode.encode('utf8')
|
|
99
|
+
try:
|
|
100
|
+
tree = config.tree_sitter_parser.parse(source_code_utf8)
|
|
101
|
+
except Exception as exc:
|
|
102
|
+
logger.warning(f"Skipping file '{path}': failed to parse with tree-sitter: {exc}")
|
|
103
|
+
continue
|
|
104
|
+
root_node = tree.root_node
|
|
105
|
+
|
|
106
|
+
code_blocks = _extract_code_blocks_rec(config, root_node, source_code_utf8, max_depth=config.max_code_blocks_ast_depth)
|
|
107
|
+
config.rng.shuffle(code_blocks) # shuffle code blocks extracted from a single file
|
|
108
|
+
for block in code_blocks:
|
|
109
|
+
yield block # yields one block at the time
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_code_blocks_from_auto_split(config: Config) -> Tuple[Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]]]:
|
|
113
|
+
"""
|
|
114
|
+
Auto-split source code files from /data into train/eval/test paths, then extract top-level
|
|
115
|
+
code blocks such as functions from each split.
|
|
116
|
+
"""
|
|
117
|
+
train_file_paths, eval_file_paths, test_file_paths = auto_create_split_paths(config)
|
|
118
|
+
train_code_blocks_iter = get_code_blocks_from_paths(config, train_file_paths)
|
|
119
|
+
eval_code_blocks_iter = get_code_blocks_from_paths(config, eval_file_paths)
|
|
120
|
+
test_code_blocks_iter = get_code_blocks_from_paths(config, test_file_paths)
|
|
121
|
+
return train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _check_required_directories(root_path: Path, required_dirs: list[str]) -> None:
|
|
125
|
+
missing_paths = []
|
|
126
|
+
for dir_name in required_dirs:
|
|
127
|
+
dir_path = root_path / dir_name
|
|
128
|
+
|
|
129
|
+
if not dir_path.is_dir():
|
|
130
|
+
missing_paths.append(str(dir_path))
|
|
131
|
+
|
|
132
|
+
if missing_paths:
|
|
133
|
+
raise FileNotFoundError(
|
|
134
|
+
f"Required directories under {root_path}. "
|
|
135
|
+
f"Missing directories: {', '.join(missing_paths)}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_filtered_paths(config: Config, directory: Path) -> list[Path]:
|
|
140
|
+
filtered_paths = []
|
|
141
|
+
|
|
142
|
+
for entry in directory.rglob("*"):
|
|
143
|
+
if not entry.is_file():
|
|
144
|
+
continue
|
|
145
|
+
if entry.suffix.lower() not in config.data_extensions:
|
|
146
|
+
continue
|
|
147
|
+
filtered_paths.append(entry)
|
|
148
|
+
|
|
149
|
+
return filtered_paths
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_code_blocks_from_manual_split(config: Config) -> Tuple[Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]]]:
|
|
153
|
+
"""
|
|
154
|
+
Extract top-level code blocks such as functions from manually split train/eval/test files.
|
|
155
|
+
"""
|
|
156
|
+
_check_required_directories(config.raw_data_path, ["train", "eval", "test"])
|
|
157
|
+
train_file_paths = _get_filtered_paths(config, config.raw_data_path / "train")
|
|
158
|
+
eval_file_paths = _get_filtered_paths(config, config.raw_data_path / "eval")
|
|
159
|
+
test_file_paths = _get_filtered_paths(config, config.raw_data_path / "test")
|
|
160
|
+
|
|
161
|
+
train_code_blocks_iter = get_code_blocks_from_paths(config, train_file_paths)
|
|
162
|
+
eval_code_blocks_iter = get_code_blocks_from_paths(config, eval_file_paths)
|
|
163
|
+
test_code_blocks_iter = get_code_blocks_from_paths(config, test_file_paths)
|
|
164
|
+
return train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Iterator, List, Mapping, Tuple
|
|
5
|
+
|
|
6
|
+
from transformers import AutoTokenizer
|
|
7
|
+
import tree_sitter as ts
|
|
8
|
+
|
|
9
|
+
from .config import Config
|
|
10
|
+
from .extract import auto_create_split_paths, get_code_blocks_from_paths
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def estimate_bytes_per_token_ratio(config: Config, tokenizer: AutoTokenizer, number_of_code_blocks: int) -> float:
|
|
17
|
+
"""
|
|
18
|
+
Estimate bytes per token ratio from first `number_of_code_blocks` code blocks in training split.
|
|
19
|
+
Always uses auto-split (ignores config.split_mode).
|
|
20
|
+
Source code is almost entirely ASCII characters (1 char = 1 byte)
|
|
21
|
+
except e.g. specific string literals (printf("π ≈ 3.14159\n");).
|
|
22
|
+
ASCII is a subset of UTF-8, so len(bytes) ≈ character count.
|
|
23
|
+
"""
|
|
24
|
+
train_file_paths, _, _ = auto_create_split_paths(config) # no matter the split mode always use the auto split
|
|
25
|
+
total_bytes = 0
|
|
26
|
+
total_tokens = 0
|
|
27
|
+
i = 0
|
|
28
|
+
block_iter = get_code_blocks_from_paths(config, train_file_paths)
|
|
29
|
+
for block in block_iter:
|
|
30
|
+
total_bytes += len(block[0]) # bytes from code block
|
|
31
|
+
tokenized_block = tokenizer(block[0].decode('utf-8')).tokens()
|
|
32
|
+
total_tokens += len(tokenized_block)
|
|
33
|
+
i += 1
|
|
34
|
+
if i >= number_of_code_blocks:
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
if total_tokens <= 0 or total_bytes <= 0:
|
|
38
|
+
raise ValueError("Failed to estimate bytes per token ratio")
|
|
39
|
+
bytes_per_token_ratio = total_bytes / total_tokens
|
|
40
|
+
return bytes_per_token_ratio
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _extract_subblock_ranges(config: Config, node: ts.Node, base_offset: int) -> list[Tuple[int, int]]:
|
|
44
|
+
"""
|
|
45
|
+
Recursively depth-first search (DFS) the Abstract syntax tree (AST) and collect all subblock indices.
|
|
46
|
+
Returns subblock ranges relative to the containing code block.
|
|
47
|
+
E.g., a subblock range of (2, 30) means the subblock starts at byte position 2
|
|
48
|
+
and ends at byte position 30, counting from the beginning of the specific code block
|
|
49
|
+
(not the file).
|
|
50
|
+
"""
|
|
51
|
+
subblock_ranges = []
|
|
52
|
+
|
|
53
|
+
if node.type in config.tree_sitter_subblock_types:
|
|
54
|
+
relative_start_byte = node.start_byte - base_offset
|
|
55
|
+
relative_end_byte = node.end_byte - base_offset
|
|
56
|
+
subblock_ranges.append((relative_start_byte, relative_end_byte))
|
|
57
|
+
for child in node.children:
|
|
58
|
+
child_subblock_ranges = _extract_subblock_ranges(config, child, base_offset)
|
|
59
|
+
subblock_ranges.extend(child_subblock_ranges)
|
|
60
|
+
|
|
61
|
+
return subblock_ranges
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _filter_subblocks(subblock_ranges: list[Tuple[int, int]], max_bytes_per_subblock: int) -> list[Tuple[int, int]]:
|
|
65
|
+
"""
|
|
66
|
+
Discard subblocks that have a larger end index than max_bytes_per_subblock
|
|
67
|
+
"""
|
|
68
|
+
subblock_ranges = sorted(subblock_ranges, key=lambda x: x[1]) # sort ranges by end index
|
|
69
|
+
i = 0
|
|
70
|
+
while i < len(subblock_ranges) and subblock_ranges[i][1] <= max_bytes_per_subblock:
|
|
71
|
+
i += 1
|
|
72
|
+
return subblock_ranges[:i]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _generate_fim_examples_from_code_block(config: Config, code_utf8: bytes, subblock_ranges: list[Tuple[int, int]], bytes_per_token_ratio: float) -> list[bytes]:
|
|
76
|
+
fim_prefix_token_utf8 = config.fim_prefix_token.encode('utf8')
|
|
77
|
+
fim_middle_token_utf8 = config.fim_middle_token.encode('utf8')
|
|
78
|
+
fim_suffix_token_utf8 = config.fim_suffix_token.encode('utf8')
|
|
79
|
+
eos_token_utf8 = config.eos_token.encode('utf8')
|
|
80
|
+
|
|
81
|
+
num_of_subblocks = len(subblock_ranges)
|
|
82
|
+
num_of_fim_examples = max(1, int(num_of_subblocks * config.fim_examples_per_subblock_ratio))
|
|
83
|
+
unique_random_subblock_indices = config.rng.choice(len(subblock_ranges), size=num_of_fim_examples, replace=False)
|
|
84
|
+
|
|
85
|
+
fim_examples = []
|
|
86
|
+
|
|
87
|
+
for idx in unique_random_subblock_indices:
|
|
88
|
+
middle_start_byte = subblock_ranges[idx][0]
|
|
89
|
+
middle_end_byte = subblock_ranges[idx][1]
|
|
90
|
+
middle_bytes_length = middle_end_byte - middle_start_byte
|
|
91
|
+
middle_tokens_length = middle_bytes_length / bytes_per_token_ratio
|
|
92
|
+
# allow only examples within a certain range
|
|
93
|
+
if (middle_tokens_length < config.min_middle_tokens_length) or (middle_tokens_length > config.max_middle_tokens_length):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
prefix = code_utf8[:middle_start_byte]
|
|
97
|
+
middle = code_utf8[middle_start_byte:middle_end_byte]
|
|
98
|
+
suffix = code_utf8[middle_end_byte:]
|
|
99
|
+
|
|
100
|
+
fim_example = (
|
|
101
|
+
fim_prefix_token_utf8 + prefix +
|
|
102
|
+
fim_suffix_token_utf8 + suffix +
|
|
103
|
+
fim_middle_token_utf8 + middle +
|
|
104
|
+
eos_token_utf8
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
fim_examples.append(fim_example)
|
|
108
|
+
|
|
109
|
+
return fim_examples
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def create_fim_examples(config: Config, code_blocks_iter: Iterator[Tuple[bytes, ts.Node]], bytes_per_token_ratio: float) -> Iterator[bytes]:
|
|
113
|
+
"""
|
|
114
|
+
Generator function that takes a generator iterator of code blocks as input,
|
|
115
|
+
generates FIM examples from each code block and returns a generator iterator
|
|
116
|
+
over all created FIM examples.
|
|
117
|
+
"""
|
|
118
|
+
for code_block_utf8, node in code_blocks_iter:
|
|
119
|
+
base_offset = node.start_byte
|
|
120
|
+
subblock_ranges = _extract_subblock_ranges(config, node, base_offset)
|
|
121
|
+
|
|
122
|
+
if not subblock_ranges:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
max_bytes_per_subblock = int(config.max_token_sequence_length * bytes_per_token_ratio)
|
|
126
|
+
|
|
127
|
+
subblock_ranges = _filter_subblocks(subblock_ranges, max_bytes_per_subblock)
|
|
128
|
+
|
|
129
|
+
code_block_utf8 = code_block_utf8[:max_bytes_per_subblock] # Trunctate code block code if it is larger than bytes_per_code_block, we only consider subblocks inside this range.
|
|
130
|
+
|
|
131
|
+
fim_examples = _generate_fim_examples_from_code_block(config, code_block_utf8, subblock_ranges, bytes_per_token_ratio)
|
|
132
|
+
for fim_example in fim_examples:
|
|
133
|
+
yield fim_example
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _save_tokenized_batch_as_jsonl(file_path: Path, batch: Mapping[str, List[List[int]]]) -> None:
|
|
137
|
+
with open(file_path, 'a', encoding='utf-8') as f:
|
|
138
|
+
batch_size = len(batch['input_ids'])
|
|
139
|
+
for i in range(batch_size):
|
|
140
|
+
input_ids = batch['input_ids'][i]
|
|
141
|
+
attention_mask = batch['attention_mask'][i]
|
|
142
|
+
labels = batch['labels'][i]
|
|
143
|
+
|
|
144
|
+
example = {
|
|
145
|
+
'input_ids': input_ids,
|
|
146
|
+
'attention_mask': attention_mask,
|
|
147
|
+
'labels': labels
|
|
148
|
+
}
|
|
149
|
+
f.write(json.dumps(example, ensure_ascii=False) + '\n') # ensre utf8 encoding
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def tokenize_and_save_fim_examples(config: Config, file_path: Path, fim_examples_iter: Iterator[bytes], tokenizer: AutoTokenizer) -> None:
|
|
153
|
+
examples_counter = 0
|
|
154
|
+
batch = []
|
|
155
|
+
for fim_example in fim_examples_iter:
|
|
156
|
+
batch.append(fim_example.decode('utf-8'))
|
|
157
|
+
examples_counter += 1
|
|
158
|
+
if (len(batch) == config.tokenizer_batch_size):
|
|
159
|
+
tokenized_batch = tokenizer(
|
|
160
|
+
batch,
|
|
161
|
+
padding=False,
|
|
162
|
+
return_tensors=None,
|
|
163
|
+
return_attention_mask=True
|
|
164
|
+
)
|
|
165
|
+
tokenized_batch["labels"] = tokenized_batch["input_ids"]
|
|
166
|
+
_save_tokenized_batch_as_jsonl(file_path, tokenized_batch)
|
|
167
|
+
batch = []
|
|
168
|
+
|
|
169
|
+
# last batch is smaller than config.tokenizer_batch_size, the "rest"
|
|
170
|
+
if batch:
|
|
171
|
+
tokenized_batch = tokenizer(
|
|
172
|
+
batch,
|
|
173
|
+
padding=False,
|
|
174
|
+
return_tensors=None,
|
|
175
|
+
return_attention_mask=True
|
|
176
|
+
)
|
|
177
|
+
tokenized_batch["labels"] = tokenized_batch["input_ids"]
|
|
178
|
+
_save_tokenized_batch_as_jsonl(file_path, tokenized_batch)
|
|
179
|
+
|
|
180
|
+
logger.info(f"Processed and saved {examples_counter} FIM examples to {file_path}")
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging.config
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from transformers import AutoTokenizer
|
|
7
|
+
|
|
8
|
+
from .config import Config
|
|
9
|
+
from .extract import get_code_blocks_from_auto_split, get_code_blocks_from_manual_split
|
|
10
|
+
from .process import create_fim_examples, estimate_bytes_per_token_ratio, tokenize_and_save_fim_examples
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _setup_logger(log_level: str) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Configure the root logger ("") to capture logs from the entry point (__main__),
|
|
19
|
+
all internal sub-modules, and third-party libraries via a single handler.
|
|
20
|
+
"""
|
|
21
|
+
logger_config = {
|
|
22
|
+
"version": 1,
|
|
23
|
+
"disable_existing_loggers": False,
|
|
24
|
+
"formatters": {
|
|
25
|
+
"standard": {
|
|
26
|
+
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"handlers": {
|
|
30
|
+
"stderr_handler": {
|
|
31
|
+
"level": log_level,
|
|
32
|
+
"class": "logging.StreamHandler",
|
|
33
|
+
"formatter": "standard",
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"loggers": {
|
|
37
|
+
"": { # "" corresponds to root logger
|
|
38
|
+
"handlers": ["stderr_handler"],
|
|
39
|
+
"level": log_level,
|
|
40
|
+
"propagate": False,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
logging.config.dictConfig(logger_config)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_args() -> argparse.Namespace:
|
|
48
|
+
parser = argparse.ArgumentParser(description="Preprocess code dataset for FIM fine-tuning.")
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--config",
|
|
51
|
+
type=Path,
|
|
52
|
+
required=True,
|
|
53
|
+
help="YAML config file path with 'preprocess:' section. All Config.MISSING fields must be provided."
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--log-level",
|
|
57
|
+
type=str,
|
|
58
|
+
default="INFO",
|
|
59
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
60
|
+
help="Logging level.",
|
|
61
|
+
)
|
|
62
|
+
return parser.parse_args()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _clear_existing_datasets(config: Config) -> None:
|
|
66
|
+
for file in [config.train_dataset_path, config.eval_dataset_path, config.test_dataset_path]:
|
|
67
|
+
if file.exists():
|
|
68
|
+
file.unlink()
|
|
69
|
+
logger.info(f"Deleted old dataset: {file}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _validate_and_configure_tokenizer(config: Config, tokenizer: AutoTokenizer):
|
|
73
|
+
required_tokens = [
|
|
74
|
+
config.fim_prefix_token,
|
|
75
|
+
config.fim_middle_token,
|
|
76
|
+
config.fim_suffix_token,
|
|
77
|
+
config.fim_pad_token,
|
|
78
|
+
config.eos_token
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
for token in required_tokens:
|
|
82
|
+
token_id = tokenizer.convert_tokens_to_ids(token)
|
|
83
|
+
|
|
84
|
+
if token_id == tokenizer.unk_token_id or token_id is None:
|
|
85
|
+
raise ValueError(f"Token '{token}' defined in Config is missing from Tokenizer vocabulary.")
|
|
86
|
+
|
|
87
|
+
tokenizer.pad_token = config.fim_pad_token
|
|
88
|
+
tokenizer.eos_token = config.eos_token
|
|
89
|
+
|
|
90
|
+
logger.info("Tokenizer validated and configured successfully.")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def run(config: Config) -> None:
|
|
94
|
+
_clear_existing_datasets(config)
|
|
95
|
+
|
|
96
|
+
if config.split_mode == "auto":
|
|
97
|
+
logger.info("Using auto-generated dataset split.")
|
|
98
|
+
train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter = get_code_blocks_from_auto_split(config)
|
|
99
|
+
else: # config.split_mode == "manual":
|
|
100
|
+
logger.info("Using manual dataset split from directories.")
|
|
101
|
+
train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter = get_code_blocks_from_manual_split(config)
|
|
102
|
+
|
|
103
|
+
tokenizer = AutoTokenizer.from_pretrained(config.model_name)
|
|
104
|
+
_validate_and_configure_tokenizer(config, tokenizer)
|
|
105
|
+
|
|
106
|
+
bytes_per_token_ratio = estimate_bytes_per_token_ratio(config, tokenizer, number_of_code_blocks=20000)
|
|
107
|
+
logger.info(f"Estimated bytes_per_token_ratio: {bytes_per_token_ratio}")
|
|
108
|
+
|
|
109
|
+
train_fim_examples_iter= create_fim_examples(config, train_code_blocks_iter, bytes_per_token_ratio)
|
|
110
|
+
eval_fim_examples_iter = create_fim_examples(config, eval_code_blocks_iter, bytes_per_token_ratio)
|
|
111
|
+
test_fim_examples_iter = create_fim_examples(config, test_code_blocks_iter, bytes_per_token_ratio)
|
|
112
|
+
|
|
113
|
+
tokenize_and_save_fim_examples(config, config.train_dataset_path, train_fim_examples_iter, tokenizer)
|
|
114
|
+
tokenize_and_save_fim_examples(config, config.eval_dataset_path, eval_fim_examples_iter, tokenizer)
|
|
115
|
+
tokenize_and_save_fim_examples(config, config.test_dataset_path, test_fim_examples_iter, tokenizer)
|
|
116
|
+
|
|
117
|
+
logger.info("Saved train, eval, test datasets to disk")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main() -> None:
|
|
121
|
+
user_args = _parse_args()
|
|
122
|
+
_setup_logger(user_args.log_level)
|
|
123
|
+
try:
|
|
124
|
+
preprocess_config = Config.load_from_yaml(user_args.config)
|
|
125
|
+
run(preprocess_config)
|
|
126
|
+
except Exception:
|
|
127
|
+
logger.exception(f"Preprocessing failed")
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
|
133
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
{
|
|
2
|
+
"c": {
|
|
3
|
+
"block_types": [
|
|
4
|
+
"function_definition",
|
|
5
|
+
"struct_specifier",
|
|
6
|
+
"union_specifier",
|
|
7
|
+
"enum_specifier"
|
|
8
|
+
],
|
|
9
|
+
"subblock_types": [
|
|
10
|
+
"compound_statement",
|
|
11
|
+
"parameter_list",
|
|
12
|
+
"declaration",
|
|
13
|
+
"expression_statement",
|
|
14
|
+
"if_statement",
|
|
15
|
+
"while_statement",
|
|
16
|
+
"for_statement",
|
|
17
|
+
"switch_statement",
|
|
18
|
+
"case_statement",
|
|
19
|
+
"return_statement",
|
|
20
|
+
"field_declaration_list",
|
|
21
|
+
"field_declaration",
|
|
22
|
+
"enumerator_list",
|
|
23
|
+
"enumerator"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"python": {
|
|
27
|
+
"block_types": [
|
|
28
|
+
"function_definition",
|
|
29
|
+
"class_definition",
|
|
30
|
+
"decorated_definition",
|
|
31
|
+
"async_function"
|
|
32
|
+
],
|
|
33
|
+
"subblock_types": [
|
|
34
|
+
"block",
|
|
35
|
+
"parameters",
|
|
36
|
+
"assignment",
|
|
37
|
+
"expression_statement",
|
|
38
|
+
"if_statement",
|
|
39
|
+
"while_statement",
|
|
40
|
+
"for_statement",
|
|
41
|
+
"match_statement",
|
|
42
|
+
"case_clause",
|
|
43
|
+
"return_statement",
|
|
44
|
+
"decorated_definition",
|
|
45
|
+
"try_statement",
|
|
46
|
+
"except_clause",
|
|
47
|
+
"finally_clause",
|
|
48
|
+
"with_statement",
|
|
49
|
+
"lambda",
|
|
50
|
+
"yield"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"cpp": {
|
|
54
|
+
"block_types": [
|
|
55
|
+
"function_definition",
|
|
56
|
+
"class_specifier",
|
|
57
|
+
"struct_specifier",
|
|
58
|
+
"union_specifier",
|
|
59
|
+
"enum_specifier",
|
|
60
|
+
"template_declaration",
|
|
61
|
+
"namespace_definition"
|
|
62
|
+
],
|
|
63
|
+
"subblock_types": [
|
|
64
|
+
"compound_statement",
|
|
65
|
+
"parameter_list",
|
|
66
|
+
"declaration",
|
|
67
|
+
"expression_statement",
|
|
68
|
+
"if_statement",
|
|
69
|
+
"while_statement",
|
|
70
|
+
"for_statement",
|
|
71
|
+
"switch_statement",
|
|
72
|
+
"case_statement",
|
|
73
|
+
"return_statement",
|
|
74
|
+
"field_declaration_list",
|
|
75
|
+
"field_declaration",
|
|
76
|
+
"enumerator_list",
|
|
77
|
+
"enumerator",
|
|
78
|
+
"try_statement",
|
|
79
|
+
"catch_statement",
|
|
80
|
+
"finally_clause"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"java": {
|
|
84
|
+
"block_types": [
|
|
85
|
+
"method_declaration",
|
|
86
|
+
"class_declaration",
|
|
87
|
+
"interface_declaration",
|
|
88
|
+
"enum_declaration",
|
|
89
|
+
"record_declaration",
|
|
90
|
+
"constructor_declaration"
|
|
91
|
+
],
|
|
92
|
+
"subblock_types": [
|
|
93
|
+
"block",
|
|
94
|
+
"formal_parameters",
|
|
95
|
+
"local_variable_declaration",
|
|
96
|
+
"expression_statement",
|
|
97
|
+
"if_statement",
|
|
98
|
+
"while_statement",
|
|
99
|
+
"for_statement",
|
|
100
|
+
"enhanced_for_statement",
|
|
101
|
+
"switch_expression",
|
|
102
|
+
"switch_rule",
|
|
103
|
+
"case_label",
|
|
104
|
+
"return_statement",
|
|
105
|
+
"throw_statement",
|
|
106
|
+
"catch_clause",
|
|
107
|
+
"finally_clause",
|
|
108
|
+
"yield_statement",
|
|
109
|
+
"try_statement",
|
|
110
|
+
"try_with_resources_statement",
|
|
111
|
+
"synchronized_statement",
|
|
112
|
+
"static_initializer"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
"vhdl": {
|
|
116
|
+
"block_types": [
|
|
117
|
+
"entity_declaration",
|
|
118
|
+
"architecture_body",
|
|
119
|
+
"process_statement",
|
|
120
|
+
"package_declaration",
|
|
121
|
+
"configuration_declaration",
|
|
122
|
+
"generate_statement"
|
|
123
|
+
],
|
|
124
|
+
"subblock_types": [
|
|
125
|
+
"signal_assignment",
|
|
126
|
+
"variable_assignment",
|
|
127
|
+
"if_statement",
|
|
128
|
+
"case_statement",
|
|
129
|
+
"loop_statement",
|
|
130
|
+
"wait_statement",
|
|
131
|
+
"assert_statement",
|
|
132
|
+
"component_instantiation",
|
|
133
|
+
"concurrent_statement",
|
|
134
|
+
"sequential_statement"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
"bash": {
|
|
138
|
+
"block_types": [
|
|
139
|
+
"function_definition",
|
|
140
|
+
"compound_statement",
|
|
141
|
+
"subshell",
|
|
142
|
+
"pipeline",
|
|
143
|
+
"list"
|
|
144
|
+
],
|
|
145
|
+
"subblock_types": [
|
|
146
|
+
"command",
|
|
147
|
+
"variable_assignment",
|
|
148
|
+
"redirected_statement",
|
|
149
|
+
"expression",
|
|
150
|
+
"assignment",
|
|
151
|
+
"test_command",
|
|
152
|
+
"negated_command",
|
|
153
|
+
"break_statement",
|
|
154
|
+
"continue_statement",
|
|
155
|
+
"return_statement",
|
|
156
|
+
"command_substitution",
|
|
157
|
+
"process_substitution",
|
|
158
|
+
"arithmetic_expansion",
|
|
159
|
+
"if_statement",
|
|
160
|
+
"while_statement",
|
|
161
|
+
"for_statement",
|
|
162
|
+
"c_style_for_statement",
|
|
163
|
+
"case_statement"
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"mojo": {
|
|
167
|
+
"block_types": [
|
|
168
|
+
"function_definition",
|
|
169
|
+
"struct_definition",
|
|
170
|
+
"trait_definition",
|
|
171
|
+
"class_definition",
|
|
172
|
+
"enum_definition"
|
|
173
|
+
],
|
|
174
|
+
"subblock_types": [
|
|
175
|
+
"compound_statement",
|
|
176
|
+
"parameter_list",
|
|
177
|
+
"declaration",
|
|
178
|
+
"expression_statement",
|
|
179
|
+
"return_statement",
|
|
180
|
+
"assignment",
|
|
181
|
+
"augmented_assignment",
|
|
182
|
+
"try_statement",
|
|
183
|
+
"catch_clause",
|
|
184
|
+
"finally_clause",
|
|
185
|
+
"decorated_definition"
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|