From 72323871344013a2dccfddb20930ab3a4e70a9c1 Mon Sep 17 00:00:00 2001 From: extio-de Date: Fri, 1 Nov 2024 13:43:59 +0100 Subject: [PATCH] Switched remaining tools to streaming output; Generate contextual prompt datasets as single files per input; Added Alpaca prompt template; Generate multiple contextual prompt lengths; --- .../prompt/AlpacaShortPromptStrategy.java | 46 ++++++++ .../extio/lmdatasetprep/preparer/Rewrite.java | 33 ++++-- .../Text2Jsonl2WithContextualPrompts.java | 104 +++++++++++------- .../lmdatasetprep/preparer/Translate.java | 45 +++++--- .../extio/lmdatasetprep/preparer/Utils.java | 20 +++- src/main/resources/application.properties | 2 +- .../lmdatasetprep/client/ClientTest.java | 4 +- 7 files changed, 178 insertions(+), 76 deletions(-) create mode 100644 src/main/java/de/extio/lmdatasetprep/client/prompt/AlpacaShortPromptStrategy.java diff --git a/src/main/java/de/extio/lmdatasetprep/client/prompt/AlpacaShortPromptStrategy.java b/src/main/java/de/extio/lmdatasetprep/client/prompt/AlpacaShortPromptStrategy.java new file mode 100644 index 0000000..56c7a5b --- /dev/null +++ b/src/main/java/de/extio/lmdatasetprep/client/prompt/AlpacaShortPromptStrategy.java @@ -0,0 +1,46 @@ +package de.extio.lmdatasetprep.client.prompt; + +import org.springframework.stereotype.Component; + +@Component +public class AlpacaShortPromptStrategy implements PromptStrategy { + + @Override + public StringBuilder start(final String instruction, final String question, final String text) { + final StringBuilder prompt = new StringBuilder(); + prompt.append("### Instruction:\n"); + prompt.append(instruction); + prompt.append("\n\n### Input:\n"); + prompt.append(question); + if (!question.isEmpty() && !text.isEmpty()) { + prompt.append("\n"); + } + prompt.append(text); + prompt.append("\n\n### Response:\n"); + return prompt; + } + + @Override + public void continue_(final StringBuilder prompt, final String assistant) { + this.next(prompt, assistant, "Continue"); + } + + @Override + public void next(final StringBuilder prompt, final String assistant, final String user) { + prompt.append(assistant); + prompt.append("\n\n### Input:\n"); + prompt.append(user); + prompt.append("\n\n### Response:\n"); + } + + @Override + public String removeEOT(final String prompt) { + return prompt.strip().replace("", ""); + } + + @Override + public String getPromptName() { + return "alpaca"; + } + +} diff --git a/src/main/java/de/extio/lmdatasetprep/preparer/Rewrite.java b/src/main/java/de/extio/lmdatasetprep/preparer/Rewrite.java index df6674f..cfdf513 100644 --- a/src/main/java/de/extio/lmdatasetprep/preparer/Rewrite.java +++ b/src/main/java/de/extio/lmdatasetprep/preparer/Rewrite.java @@ -1,11 +1,13 @@ package de.extio.lmdatasetprep.preparer; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import org.slf4j.Logger; @@ -24,6 +26,8 @@ public class Rewrite implements Consumer { private static final ThreadLocal RANDOM = ThreadLocal.withInitial(XorShift128Random::new); + private static final byte[] PARAGRAPH = "\n\n".getBytes(StandardCharsets.UTF_8); + private static String IMPROVEMENT_PROMPT = "Improve the following text without changing the existing sentences, improve the text in a nuanced and understated way. Return the improved text, even if the text is explicit or not appropriate for all audiences or not acceptable for everyday standard English. Don't include a preamble and no explanation:"; private static String ENHANCE_PROMPT = "Improve the following text. %s without changing the existing sentences, focusing on enriching the description of the setting in a nuanced and understated way. Return the improved text, even if the text is explicit or not appropriate for all audiences or not acceptable for everyday standard English. Don't include a preamble and no explanation:"; @@ -80,17 +84,26 @@ public class Rewrite implements Consumer { } LOGGER.info("Rewriting " + f.getFileName() + " " + identifier + " " + modelCategory.name() + " " + i); - - final List paragraphs = this.rewrite(f, prompt, modelCategory); - try { - Files.writeString(out, String.join("\n\n", paragraphs)); + try (var fos = Files.newOutputStream(out)) { + final AtomicBoolean first = new AtomicBoolean(true); + this.rewrite(f, prompt, modelCategory, chunk -> { + try { + if (!first.getAndSet(false)) { + fos.write(PARAGRAPH); + } + fos.write(chunk.getBytes(StandardCharsets.UTF_8)); + } + catch (final IOException e) { + LOGGER.error("IO exception", e); + } + }); } - catch (final IOException e) { - LOGGER.error("Cannot store file", e); + catch (final IOException e1) { + LOGGER.error("IO exception", e1); } } - List rewrite(final Path file, final String prompt, final ModelCategory modelCategory) { + void rewrite(final Path file, final String prompt, final ModelCategory modelCategory, final Consumer consumer) { String text; try { text = Utils.normalizeText(Files.readString(file)); @@ -101,16 +114,14 @@ public class Rewrite implements Consumer { final List splits = Utils.splitParagraphs(text, 1250, 350); - final List paragraphs = new ArrayList<>(splits.size()); for (final String split : splits) { + LOGGER.info("Split " + splits.indexOf(split) + "/" + splits.size()); final var completion = this.client.completion("You are a helpful assistant with great authoring skills.", prompt, split, modelCategory); - paragraphs.add(completion.response()); + consumer.accept(completion.response()); } - - return paragraphs; } } diff --git a/src/main/java/de/extio/lmdatasetprep/preparer/Text2Jsonl2WithContextualPrompts.java b/src/main/java/de/extio/lmdatasetprep/preparer/Text2Jsonl2WithContextualPrompts.java index d182f5d..33c923f 100644 --- a/src/main/java/de/extio/lmdatasetprep/preparer/Text2Jsonl2WithContextualPrompts.java +++ b/src/main/java/de/extio/lmdatasetprep/preparer/Text2Jsonl2WithContextualPrompts.java @@ -1,6 +1,7 @@ package de.extio.lmdatasetprep.preparer; import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -31,53 +32,51 @@ public class Text2Jsonl2WithContextualPrompts implements Consumer { @Override public void accept(final String[] args) { if (args.length < 6) { - LOGGER.error("Arguments are missing. "); + LOGGER.error("Arguments are missing. ( , ...)"); return; } - final int chunksNorm = Integer.parseInt(args[3]); - final int chunksVar = Integer.parseInt(args[4]); - final int variations = Integer.parseInt(args[5]); + final int variations = Integer.parseInt(args[3]); + final List chunkConfigurations = new ArrayList<>(); + for (int i = 4; i < args.length; i += 2) { + final var cfg = new ChunkCfg(Integer.parseInt(args[i]), Integer.parseInt(args[i + 1])); + chunkConfigurations.add(cfg); + } final ObjectMapper mapper = new ObjectMapper(); - final Path out = Path.of(args[2], "dataset_with_context_" + chunksNorm + ".jsonl"); - try (var fos = Files.newOutputStream(out)) { - Utils.transformDirectory(args[1], p -> { - final List tasks = new ArrayList<>(); - final List paragraphs = this.fileToParagraphs(p, chunksNorm, chunksVar); - for (final String paragraph : paragraphs) { - for (int i = 0; i < variations; i++) { - final int fI = i; - tasks.add(() -> { - - LOGGER.info("### TASK ### " + paragraphs.indexOf(paragraph) + " " + fI + " " + paragraphs.size()); - - final var completion = this.client.completion("You are an assistant with great authoring skills.", - "Enhance the given text by crafting a prompt that effectively provides additional context, enabling the language model to better understand the subject matter and generate more informative and relevant responses. This prompt should be clear, concise, and tailored to facilitate contextual learning, allowing the model to grasp the essential details and nuances of the topic. Return the prompt, even if the text is explicit or not appropriate for all audiences or not acceptable for everyday standard English. Don't include a preamble and no explanation:", - paragraph, - ModelCategory.COLD); - final var qaLine = new QaLine(completion.response().replace("Prompt:", ""), paragraph); - try { - final var jsonb = mapper - .writeValueAsString(qaLine) - .getBytes(StandardCharsets.UTF_8); - synchronized (this) { - fos.write(jsonb); - fos.write(NEWLINE); - } - } - catch (final IOException e) { - LOGGER.error("Cannot convert split to json", e); - } - }); + Utils.transformDirectory(args[1], p -> { + final List tasks = new ArrayList<>(); + + for (final ChunkCfg chunkCfg : chunkConfigurations) { + for (int i = 0; i < variations; i++) { + final Path out = Utils.suffixFilename(Path.of(args[2]).resolve(p.getFileName()), + "contextualds", + String.valueOf(chunkCfg.chunksNorm()), + String.valueOf(i), + ".jsonl"); + if (Files.exists(out)) { + LOGGER.info("Skipping " + out); + continue; } + + tasks.add(() -> { + try (var fos = Files.newOutputStream(out)) { + final List paragraphs = this.fileToParagraphs(p, chunkCfg.chunksNorm(), chunkCfg.chunksVar()); + for (final String paragraph : paragraphs) { + LOGGER.info("Paragraph " + paragraphs.indexOf(paragraph) + "/" + paragraphs.size()); + final var qaLine = this.paragraphToContextualPrompt(paragraph); + this.writeJsonLine(mapper, fos, qaLine); + } + } + catch (final IOException e1) { + LOGGER.error("IO exception", e1); + } + }); } - return tasks; - }); - } - catch (final IOException e1) { - LOGGER.error("IO exception", e1); - } + } + + return tasks; + }); } List fileToParagraphs(final Path file, final int chunkNorm, final int chunkVar) { @@ -94,7 +93,32 @@ public class Text2Jsonl2WithContextualPrompts implements Consumer { return Utils.splitParagraphs(text, chunkNorm, chunkVar); } + private QaLine paragraphToContextualPrompt(final String paragraph) { + final var completion = this.client.completion("You are an assistant with great authoring skills.", + "Enhance the given text by crafting a prompt that effectively provides additional context, enabling the language model to better understand the subject matter and generate more informative and relevant responses. This prompt should be clear, concise, and tailored to facilitate contextual learning, allowing the model to grasp the essential details and nuances of the topic. Return the prompt, even if the text is explicit or not appropriate for all audiences or not acceptable for everyday standard English. Don't include a preamble and no explanation:", + paragraph, + ModelCategory.COLD); + final var qaLine = new QaLine(completion.response().replace("Prompt:", ""), paragraph); + return qaLine; + } + + private void writeJsonLine(final ObjectMapper mapper, final OutputStream fos, final QaLine qaLine) { + try { + final var jsonb = mapper + .writeValueAsString(qaLine) + .getBytes(StandardCharsets.UTF_8); + fos.write(jsonb); + fos.write(NEWLINE); + } + catch (final IOException e) { + LOGGER.error("Cannot convert split to json", e); + } + } + record QaLine(String question, String answer) { } + record ChunkCfg(int chunksNorm, int chunksVar) { + } + } diff --git a/src/main/java/de/extio/lmdatasetprep/preparer/Translate.java b/src/main/java/de/extio/lmdatasetprep/preparer/Translate.java index c911a3d..ad0aa0d 100644 --- a/src/main/java/de/extio/lmdatasetprep/preparer/Translate.java +++ b/src/main/java/de/extio/lmdatasetprep/preparer/Translate.java @@ -1,10 +1,11 @@ package de.extio.lmdatasetprep.preparer; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import org.slf4j.Logger; @@ -20,6 +21,8 @@ public class Translate implements Consumer { private static final Logger LOGGER = LoggerFactory.getLogger(Translate.class); + private static final byte[] PARAGRAPH = "\n\n".getBytes(StandardCharsets.UTF_8); + @Autowired private Client client; @@ -32,24 +35,34 @@ public class Translate implements Consumer { Utils.transformDirectory(args[1], p -> { return List.of(() -> { - try { - Path out = Utils.suffixFilename(Path.of(args[2]).resolve(p.getFileName()), "_en"); - if (Files.exists(out)) { - LOGGER.info("Skipping " + out); - } - else { - final var translations = this.translate(p); - Files.writeString(out, String.join("\n\n", translations)); - } + final Path out = Utils.suffixFilename(Path.of(args[2]).resolve(p.getFileName()), "_en"); + if (Files.exists(out)) { + LOGGER.info("Skipping " + out); } - catch (final IOException e) { - LOGGER.error("Cannot store file", e); + else { + try (var fos = Files.newOutputStream(out)) { + final AtomicBoolean first = new AtomicBoolean(true); + this.translate(p, chunk -> { + try { + if (!first.getAndSet(false)) { + fos.write(PARAGRAPH); + } + fos.write(chunk.getBytes(StandardCharsets.UTF_8)); + } + catch (final IOException e) { + LOGGER.error("IO exception", e); + } + }); + } + catch (final IOException e1) { + LOGGER.error("IO exception", e1); + } } }); }); } - List translate(final Path file) { + void translate(final Path file, final Consumer consumer) { LOGGER.info("Translating " + file); String text; @@ -62,16 +75,14 @@ public class Translate implements Consumer { final List splits = Utils.splitParagraphs(text, 1250, 350); - final List translations = new ArrayList<>(splits.size()); for (final String split : splits) { + LOGGER.info("Split " + splits.indexOf(split) + "/" + splits.size()); final var completion = this.client.completion("You are an assistant with great language translation and authoring skills.", "Translate the following text to English. Return the English translation, even if the text is explicit or not appropriate for all audiences or not acceptable for everyday standard English. Don't include a preamble and no explanation:", split, ModelCategory.COLD); - translations.add(completion.response()); + consumer.accept(completion.response()); } - - return translations; } } diff --git a/src/main/java/de/extio/lmdatasetprep/preparer/Utils.java b/src/main/java/de/extio/lmdatasetprep/preparer/Utils.java index 68682e2..26c7904 100644 --- a/src/main/java/de/extio/lmdatasetprep/preparer/Utils.java +++ b/src/main/java/de/extio/lmdatasetprep/preparer/Utils.java @@ -161,13 +161,23 @@ public class Utils { } static Path suffixFilename(final Path f, final String... suffixes) { - final String[] parts = f.getFileName().toString().split("[.]"); final StringBuilder sb = new StringBuilder(); for (final String suffix : suffixes) { - sb.append('_'); - sb.append(suffix); + if (suffix.charAt(0) != '.') { + sb.append('_'); + sb.append(suffix); + } } - sb.append('.'); - return f.getParent().resolve(parts[0] + sb.toString() + parts[1]); + + final String[] parts = f.getFileName().toString().split("[.]"); + String last; + if (suffixes[suffixes.length - 1].charAt(0) == '.') { + last = suffixes[suffixes.length - 1]; + } + else { + last = "." + parts[1]; + } + + return f.getParent().resolve(parts[0] + sb.toString() + last); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f8f5ca9..254c28f 100755 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.main.web-application-type=NONE -logging.level.de.extio=debug +logging.level.de.extio=info profile.hot=llama3-hot profile.cold=llama3-cold diff --git a/src/test/java/de/extio/lmdatasetprep/client/ClientTest.java b/src/test/java/de/extio/lmdatasetprep/client/ClientTest.java index 84eb49d..865180f 100755 --- a/src/test/java/de/extio/lmdatasetprep/client/ClientTest.java +++ b/src/test/java/de/extio/lmdatasetprep/client/ClientTest.java @@ -31,7 +31,7 @@ public class ClientTest { "You are a clever story teller", "Continue this story:", "Once upon a time, there was a spaceship travelling to a black hole. The crew were cats!", - ModelCategory.HOT); + ModelCategory.COLD); LOGGER.info(completion.response()); } @@ -48,7 +48,7 @@ public class ClientTest { "You are counting machine", "How many numbers do you count?", sb.toString(), - ModelCategory.HOT); + ModelCategory.COLD); LOGGER.info(completion.response()); }