Switch dataset preparer to lmlib;

This commit is contained in:
extio-de
2024-12-18 20:05:03 +01:00
parent fb34561bd5
commit 75c2109d6d
38 changed files with 248 additions and 1679 deletions

View File

@@ -11,7 +11,8 @@
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<springdoc.version>2.5.0</springdoc.version> <springdoc.version>2.5.0</springdoc.version>
<spring-boot.version>3.3.4</spring-boot.version> <spring-boot.version>3.4.0</spring-boot.version>
<lmlib.version>1.0.0-SNAPSHOT</lmlib.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -36,6 +37,12 @@
<artifactId>caffeine</artifactId> <artifactId>caffeine</artifactId>
</dependency> </dependency>
<dependency>
<groupId>de.extio</groupId>
<artifactId>lm-lib</artifactId>
<version>${lmlib.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View File

@@ -11,9 +11,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication @SpringBootApplication
@EnableCaching @EnableCaching
@ComponentScan(basePackages = { "de.extio.lmdatasetprep", "de.extio.lmlib" })
public class LmDatasetPreparerApplication implements CommandLineRunner, ApplicationContextAware { public class LmDatasetPreparerApplication implements CommandLineRunner, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(LmDatasetPreparerApplication.class); private static final Logger LOGGER = LoggerFactory.getLogger(LmDatasetPreparerApplication.class);
@@ -22,7 +24,7 @@ public class LmDatasetPreparerApplication implements CommandLineRunner, Applicat
SpringApplication.run(LmDatasetPreparerApplication.class, args); SpringApplication.run(LmDatasetPreparerApplication.class, args);
} }
private ApplicationContext applicationContext; public static ApplicationContext applicationContext;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
@@ -32,9 +34,9 @@ public class LmDatasetPreparerApplication implements CommandLineRunner, Applicat
return; return;
} }
final var bean = this.applicationContext.getBean(args[0]); final var bean = applicationContext.getBean(args[0]);
if (!(bean instanceof Consumer)) { if (!(bean instanceof Consumer)) {
LOGGER.info("Invalid bean {}", args[0]); LOGGER.error("Invalid bean {}", args[0]);
return; return;
} }
@@ -42,14 +44,14 @@ public class LmDatasetPreparerApplication implements CommandLineRunner, Applicat
try { try {
((Consumer<String[]>) bean).accept(args); ((Consumer<String[]>) bean).accept(args);
} }
catch (Exception e) { catch (final Exception e) {
LOGGER.error("Error executing the component", e); LOGGER.error("Error executing the component", e);
} }
} }
@Override @Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(final ApplicationContext appContext) throws BeansException {
this.applicationContext = applicationContext; applicationContext = appContext;
} }
} }

View File

@@ -1,62 +0,0 @@
package de.extio.lmdatasetprep;
import java.util.Random;
import java.util.UUID;
@SuppressWarnings("serial")
public final class XorShift128Random extends Random {
private long state0;
private long state1;
public XorShift128Random() {
super();
}
public XorShift128Random(final long seed) {
this.setSeed(seed);
}
public XorShift128Random(final String state) {
final UUID uuid = UUID.fromString(state);
this.state0 = uuid.getMostSignificantBits();
this.state1 = uuid.getLeastSignificantBits();
}
@Override
public synchronized void setSeed(final long seed) {
this.state0 = avalanche(seed == 0 ? -1 : seed);
this.state1 = avalanche(this.state0);
this.state0 = avalanche(this.state1);
}
@Override
protected int next(final int bits) {
return (int) (this.nextLong() & (1L << bits) - 1);
}
@Override
public long nextLong() {
long s1 = this.state0;
final long s0 = this.state1;
this.state0 = s0;
s1 ^= s1 << 23;
return (this.state1 = s1 ^ s0 ^ (s1 >>> 17) ^ (s0 >>> 26)) + s0;
}
@Override
public String toString() {
return new UUID(this.state0, this.state1).toString();
}
private static long avalanche(long k) {
k ^= k >>> 33;
k *= 0xff51afd7ed558ccdL;
k ^= k >>> 33;
k *= 0xc4ceb9fe1a85ec53L;
k ^= k >>> 33;
return k;
}
}

View File

@@ -1,225 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import de.extio.lmdatasetprep.client.dto.Choice;
import de.extio.lmdatasetprep.client.dto.CompletionRequest;
import de.extio.lmdatasetprep.client.dto.CompletionResponse;
import de.extio.lmdatasetprep.client.profile.ModelCategory;
import de.extio.lmdatasetprep.client.profile.ModelProfile;
import de.extio.lmdatasetprep.client.profile.ModelProfileService;
import de.extio.lmdatasetprep.client.prompt.PromptStrategy;
import de.extio.lmdatasetprep.client.prompt.PromptStrategyFactory;
@Component
public class Client implements DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
@Autowired
private WebClient.Builder webClientBuilder;
@Autowired
private PromptStrategyFactory promptStrategyFactory;
@Autowired
private ModelNameSupplier modelNameSupplier;
@Autowired
private Tokenizer tokenizer;
@Autowired
private ModelProfileService modelProfileService;
@Value("${client.collectStatistics}")
private boolean collectStatistics;
private final Map<String, Integer> promptTemplateTokenLengths = new ConcurrentHashMap<>();
private final CompletionStatistics totalStatistics = new CompletionStatistics();
public Completion completion(final String instruction, final String question, final String fullText, final ModelCategory modelCategory) {
final var modelProfile = this.modelProfileService.getModelProfile(modelCategory.getModelProfile());
final PromptStrategy promptStrategy = this.promptStrategyFactory.getStrategy(modelProfile.prompt());
final List<String> texts = new ArrayList<>();
final List<Long> tokenizedInstrAndQuestion = this.tokenizer.tokenize(instruction + question, modelProfile);
final List<Long> tokenizedFullText = this.tokenizer.tokenize(fullText, modelProfile);
final int inputTokens = tokenizedInstrAndQuestion.size() + tokenizedFullText.size() + this.getPromptTemplateTokenLength(modelProfile);
if (inputTokens > modelProfile.maxContextLength() - modelProfile.maxTokens()) {
final int steps = modelProfile.maxContextLength() - modelProfile.maxTokens() - tokenizedInstrAndQuestion.size() - this.getPromptTemplateTokenLength(modelProfile);
for (int i = 0; i < tokenizedFullText.size(); i += steps) {
final List<Long> split = tokenizedFullText.subList(i, Math.min(i + steps, tokenizedFullText.size()));
texts.add(this.tokenizer.detokenize(split, modelProfile));
}
}
else {
texts.add(fullText);
}
LOGGER.info("Completion request. Input tokens: {}", inputTokens);
LOGGER.debug("Splitted text into parts: " + texts.size());
final List<StringBuilder> answers = new ArrayList<>();
final CompletionStatistics statistics = new CompletionStatistics();
for (final String text : texts) {
final StringBuilder answer = new StringBuilder();
final StringBuilder prompt = promptStrategy.start(instruction, question, text);
this.requestCompletionsContinuations(prompt, answer, statistics, modelProfile, promptStrategy);
answers.add(answer);
}
if (answers.size() > 1) {
LOGGER.debug("Summarizing answers");
final StringBuilder summary = new StringBuilder();
final StringBuilder prompt = promptStrategy.start(instruction, question, "");
answers.forEach(answer -> promptStrategy.next(prompt, answer.toString(), "Continue"));
promptStrategy.next(prompt, "", "Generate now a full summary");
this.requestCompletionsContinuations(prompt, summary, statistics, modelProfile, promptStrategy);
answers.clear();
answers.add(summary);
}
return new Completion(
answers.stream().collect(Collectors.joining()),
statistics.requests,
statistics.duration,
statistics.inTokens,
statistics.outTokens,
false);
}
private void requestCompletionsContinuations(final StringBuilder prompt, final StringBuilder answer, final CompletionStatistics statistics,
final ModelProfile modelProfile, final PromptStrategy promptStrategy) {
for (int continuation = 0; continuation < modelProfile.maxContinuations(); continuation++) {
final LocalDateTime start = LocalDateTime.now();
final String promptStr = prompt.toString();
final var response = this.requestCompletions(promptStr, modelProfile);
String content = null;
var complete = false;
if (response.getContent() != null) {
content = promptStrategy.removeEOT(response.getContent());
complete = response.isStoppedEos();
}
else {
final var choice = response.getChoices().getFirst();
content = promptStrategy.removeEOT(choice.getText());
complete = !Choice.FINISH_REASON_LENGTH.equals(choice.getFinishReason());
}
if (this.collectStatistics) {
final var dur = Duration.between(start, LocalDateTime.now());
final var in = this.tokenizer.count(promptStr, modelProfile);
final var out = this.tokenizer.count(content, modelProfile);
statistics.add(dur, in, out);
this.totalStatistics.add(dur, in, out);
}
answer.append(content);
if (complete) {
LOGGER.debug("Prompt response complete");
break;
}
answer.append('\n');
promptStrategy.continue_(prompt, content);
}
}
private CompletionResponse requestCompletions(final String prompt, final ModelProfile modelProfile) {
LOGGER.debug("Requesting completion");
final var webClient = this.webClientBuilder.baseUrl(modelProfile.baseUrl()).build();
final var request = new CompletionRequest();
request.setModel(this.modelNameSupplier.getModelName(modelProfile.baseUrl()));
request.setPrompt(prompt);
request.setMaxTokens(modelProfile.maxTokens());
request.setTemperature(modelProfile.temperature());
request.setTopP(modelProfile.topP());
request.setStream(false);
return webClient
.method(HttpMethod.POST)
.uri(uriBuilder -> uriBuilder.path("/v1/completions").build())
.bodyValue(request)
.retrieve()
.bodyToMono(CompletionResponse.class)
.block();
}
private int getPromptTemplateTokenLength(final ModelProfile modelProfile) {
return this.promptTemplateTokenLengths.computeIfAbsent(modelProfile.prompt(), key -> {
final PromptStrategy promptStrategy = this.promptStrategyFactory.getStrategy(modelProfile.prompt());
final var prompt = promptStrategy.start(" ", " ", " ");
return this.tokenizer.count(prompt.toString(), modelProfile);
});
}
@Override
public void destroy() throws Exception {
if (this.collectStatistics) {
LOGGER.info("Client Statistics: {}", this.totalStatistics);
}
}
static class CompletionStatistics {
volatile int requests;
volatile Duration duration = Duration.ofMillis(0l);
volatile long inTokens;
volatile long outTokens;
synchronized void add(final Duration duration, final int inTokens, final int outTokens) {
LOGGER.debug("Request duration: " + duration + "; in tokens: " + inTokens + "; out tokens: " + outTokens);
this.requests++;
this.duration = this.duration.plus(duration);
this.inTokens += inTokens;
this.outTokens += outTokens;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("CompletionStatistics [requests=");
builder.append(this.requests);
builder.append(", duration=");
builder.append(this.duration);
builder.append(", inTokens=");
builder.append(this.inTokens);
builder.append(", outTokens=");
builder.append(this.outTokens);
builder.append("]");
return builder.toString();
}
}
}

View File

@@ -1,38 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import reactor.netty.http.client.HttpClient;
@Configuration
public class ClientConfiguration {
private static final int TIMEOUT = 900000;
private static final int CONNECT_TIMEOUT = 10000;
@Bean
WebClient.Builder webClientBuilder() {
final HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT)
.responseTimeout(Duration.ofMillis(TIMEOUT))
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS)).addHandlerLast(new WriteTimeoutHandler(TIMEOUT, TimeUnit.MILLISECONDS)));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
}
}

View File

@@ -1,7 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.time.Duration;
public record Completion(String response, int requests, Duration duration, long inTokens, long outTokens, boolean cached) {
}

View File

@@ -1,61 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;
import de.extio.lmdatasetprep.client.dto.Model;
import de.extio.lmdatasetprep.client.dto.ModelsResponse;
public class ModelNameLoader implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelNameLoader.class);
private final WebClient webClient;
private final Consumer<String> consumer;
public ModelNameLoader(final WebClient webClient, final Consumer<String> consumer) {
this.consumer = consumer;
this.webClient = webClient;
}
@Override
public void run() {
String modelName = null;
do {
try {
LOGGER.info("Querying model name");
modelName = this.webClient
.method(HttpMethod.GET)
.uri(uriBuilder -> uriBuilder.path("/v1/models").build())
.retrieve()
.bodyToMono(ModelsResponse.class)
.block()
.getData()
.stream()
.map(Model::getId)
.findFirst()
.orElseThrow();
}
catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
try {
Thread.sleep(1000l);
}
catch (final InterruptedException e1) {
throw new RuntimeException("Interrupted", e1);
}
}
}
while (modelName == null);
LOGGER.info("Model name: {}", modelName);
this.consumer.accept(modelName);
}
}

View File

@@ -1,58 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import de.extio.lmdatasetprep.client.profile.ModelProfileService;
@Component
public class ModelNameSupplier implements InitializingBean {
@Autowired
private WebClient.Builder webClientBuilder;
private final Map<String, CompletableFuture<String>> modelNames = new ConcurrentHashMap<>();
private final Map<String, CountDownLatch> countDownLatches = new ConcurrentHashMap<>();
@Autowired
private ModelProfileService modelProfileService;
@Override
public void afterPropertiesSet() throws Exception {
final var urls = this.modelProfileService.getModelProfileUrls();
for (final var url : urls) {
this.countDownLatches.put(url, new CountDownLatch(1));
this.fetchModelName(url);
}
}
private void fetchModelName(final String url) {
final var webClient = this.webClientBuilder.baseUrl(url).build();
CompletableFuture.runAsync(() -> {
final var modelNameLoader = new ModelNameLoader(webClient, modelName -> {
this.modelNames.put(url, CompletableFuture.completedFuture(modelName));
this.countDownLatches.get(url).countDown();
});
modelNameLoader.run();
});
}
public String getModelName(final String url) {
try {
this.countDownLatches.get(url).await();
}
catch (final InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
return this.modelNames.get(url).join();
}
}

View File

@@ -1,66 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import de.extio.lmdatasetprep.client.profile.ModelProfile;
@Component
public class Tokenizer {
private static final Logger LOGGER = LoggerFactory.getLogger(Tokenizer.class);
@Autowired
private WebClient.Builder webClientBuilder;
public List<Long> tokenize(final String txt, final ModelProfile modelProfile) {
LOGGER.debug("Requesting tokenizing");
final var webClient = this.webClientBuilder.baseUrl(modelProfile.baseUrl()).build();
final var request = new TokenizeRequest(txt);
final var response = webClient
.method(HttpMethod.POST)
.uri(uriBuilder -> uriBuilder.path("/tokenize").build())
.bodyValue(request)
.retrieve()
.bodyToMono(TokenizeResponse.class)
.block();
return response.tokens();
}
public int count(final String txt, final ModelProfile modelProfile) {
return this.tokenize(txt, modelProfile).size();
}
public String detokenize(final List<Long> tokens, final ModelProfile modelProfile) {
LOGGER.debug("Requesting detokenizing");
final var webClient = this.webClientBuilder.baseUrl(modelProfile.baseUrl()).build();
final var request = new DetokenizeRequest(tokens);
final var response = webClient
.method(HttpMethod.POST)
.uri(uriBuilder -> uriBuilder.path("/detokenize").build())
.bodyValue(request)
.retrieve()
.bodyToMono(DetokenizeResponse.class)
.block();
return response.content();
}
private record TokenizeRequest(String content) {
}
private record TokenizeResponse(List<Long> tokens) {
}
private record DetokenizeRequest(List<Long> tokens) {
}
private record DetokenizeResponse(String content) {
}
}

View File

@@ -1,59 +0,0 @@
package de.extio.lmdatasetprep.client.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Choice {
public static final String FINISH_REASON_STOP = "stop";
public static final String FINISH_REASON_LENGTH = "length";
public static final String FINISH_REASON_CONTENT_FILTER = "content_filter";
private String text;
private Double score;
private String finishReason;
@JsonProperty("text")
public String getText() {
return this.text;
}
public void setText(final String text) {
this.text = text;
}
@JsonProperty("score")
public Double getScore() {
return this.score;
}
public void setScore(final Double score) {
this.score = score;
}
@JsonProperty("finish_reason")
public String getFinishReason() {
return this.finishReason;
}
public void setFinishReason(final String finishReason) {
this.finishReason = finishReason;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Choice [text=");
builder.append(this.text);
builder.append(", score=");
builder.append(this.score);
builder.append(", finishReason=");
builder.append(this.finishReason);
builder.append("]");
return builder.toString();
}
}

View File

@@ -1,72 +0,0 @@
package de.extio.lmdatasetprep.client.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CompletionRequest {
private String model;
private String prompt;
private Integer maxTokens;
private Double temperature;
private Double topP;
private Boolean stream;
@JsonProperty("model")
public String getModel() {
return this.model;
}
public void setModel(final String model) {
this.model = model;
}
@JsonProperty("prompt")
public String getPrompt() {
return this.prompt;
}
public void setPrompt(final String prompt) {
this.prompt = prompt;
}
@JsonProperty("max_tokens")
public Integer getMaxTokens() {
return this.maxTokens;
}
public void setMaxTokens(final Integer maxTokens) {
this.maxTokens = maxTokens;
}
@JsonProperty("temperature")
public Double getTemperature() {
return this.temperature;
}
public void setTemperature(final Double temperature) {
this.temperature = temperature;
}
@JsonProperty("top_p")
public Double getTopP() {
return this.topP;
}
public void setTopP(final Double topP) {
this.topP = topP;
}
@JsonProperty("stream")
public Boolean isStream() {
return this.stream;
}
public void setStream(final Boolean stream) {
this.stream = stream;
}
}

View File

@@ -1,93 +0,0 @@
package de.extio.lmdatasetprep.client.dto;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CompletionResponse {
private String id;
private String object;
private Integer created;
private List<Choice> choices;
private String content;
private boolean isStoppedEos;
@JsonProperty("id")
public String getId() {
return this.id;
}
public void setId(final String id) {
this.id = id;
}
@JsonProperty("object")
public String getObject() {
return this.object;
}
public void setObject(final String object) {
this.object = object;
}
@JsonProperty("created")
public Integer getCreated() {
return this.created;
}
public void setCreated(final Integer created) {
this.created = created;
}
@JsonProperty("choices")
public List<Choice> getChoices() {
return this.choices;
}
public void setChoices(final List<Choice> choices) {
this.choices = choices;
}
@JsonProperty("content")
public String getContent() {
return this.content;
}
public void setContent(final String content) {
this.content = content;
}
@JsonProperty("stopped_eos")
public boolean isStoppedEos() {
return this.isStoppedEos;
}
public void setStoppedEos(final boolean isStoppedEos) {
this.isStoppedEos = isStoppedEos;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("CompletionResponse [id=");
builder.append(this.id);
builder.append(", object=");
builder.append(this.object);
builder.append(", created=");
builder.append(this.created);
builder.append(", choices=");
builder.append(this.choices);
builder.append(", content=");
builder.append(this.content);
builder.append(", isStoppedEos=");
builder.append(this.isStoppedEos);
builder.append("]");
return builder.toString();
}
}

View File

@@ -1,74 +0,0 @@
package de.extio.lmdatasetprep.client.dto;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Model {
private String id;
private String object;
private int created;
private String ownedBy;
private List<String> permission;
private String root;
@JsonProperty("id")
public String getId() {
return this.id;
}
public void setId(final String id) {
this.id = id;
}
@JsonProperty("object")
public String getObject() {
return this.object;
}
public void setObject(final String object) {
this.object = object;
}
@JsonProperty("created")
public int getCreated() {
return this.created;
}
public void setCreated(final int created) {
this.created = created;
}
@JsonProperty("owned_by")
public String getOwned_by() {
return this.ownedBy;
}
public void setOwned_by(final String owned_by) {
this.ownedBy = owned_by;
}
@JsonProperty("permission")
public List<String> getPermission() {
return this.permission;
}
public void setPermission(final List<String> permission) {
this.permission = permission;
}
@JsonProperty("root")
public String getRoot() {
return this.root;
}
public void setRoot(final String root) {
this.root = root;
}
}

View File

@@ -1,42 +0,0 @@
package de.extio.lmdatasetprep.client.dto;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ModelsResponse {
private String object;
private List<Model> data;
@JsonProperty("object")
public String getObject() {
return this.object;
}
public void setObject(final String object) {
this.object = object;
}
@JsonProperty("data")
public List<Model> getData() {
return this.data;
}
public void setData(final List<Model> data) {
this.data = data;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("ModelsResponse [object=");
builder.append(this.object);
builder.append(", data=");
builder.append(this.data);
builder.append("]");
return builder.toString();
}
}

View File

@@ -1,16 +0,0 @@
package de.extio.lmdatasetprep.client.profile;
public enum ModelCategory {
HOT("profile.hot"),
COLD("profile.cold");
private final String modelProfile;
ModelCategory(final String modelProfile) {
this.modelProfile = modelProfile;
}
public String getModelProfile() {
return this.modelProfile;
}
}

View File

@@ -1,11 +0,0 @@
package de.extio.lmdatasetprep.client.profile;
public record ModelProfile(
String prompt,
int maxTokens,
int maxContextLength,
double temperature,
double topP,
int maxContinuations,
String baseUrl) {
}

View File

@@ -1,95 +0,0 @@
package de.extio.lmdatasetprep.client.profile;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.stereotype.Service;
@Service
public class ModelProfileService {
private final Environment environment;
private static final String MODEL_PROFILE_PREFIX = "profile";
private static final Logger LOGGER = LoggerFactory.getLogger(ModelProfileService.class);
public ModelProfileService(final Environment environment) {
this.environment = environment;
}
@Cacheable("modelProfiles")
public ModelProfile getModelProfile(final String modelProfile) {
final var modelName = this.environment.getProperty(modelProfile);
if (modelName == null) {
throw new IllegalStateException("No model found for profile: " + modelProfile);
}
try {
final var resource = new ResourcePropertySource("classpath:" + modelName + ".properties");
final var name = Optional.ofNullable(resource.getProperty("prompts"))
.map(Object::toString)
.orElseThrow(() -> new IllegalStateException("No model name found for model: " + modelName));
final var maxTokens = Optional.ofNullable(resource.getProperty("maxTokens"))
.map(Object::toString)
.map(Integer::parseInt)
.orElseThrow(() -> new IllegalStateException("No model maxTokens found for model: " + modelName));
final var maxContextLength = Optional.ofNullable(resource.getProperty("maxContextLength"))
.map(Object::toString)
.map(Integer::parseInt)
.orElseThrow(() -> new IllegalStateException("No model maxContextLength found for model: " + modelName));
final var temperature = Optional.ofNullable(resource.getProperty("temperature"))
.map(Object::toString)
.map(Double::parseDouble)
.orElseThrow(() -> new IllegalStateException("No model temperature found for model: " + modelName));
final var topP = Optional.ofNullable(resource.getProperty("topP"))
.map(Object::toString)
.map(Double::parseDouble)
.orElseThrow(() -> new IllegalStateException("No model topP found for model: " + modelName));
final var maxContinuations = Optional.ofNullable(resource.getProperty("maxContinuations"))
.map(Object::toString)
.map(Integer::parseInt)
.orElseThrow(() -> new IllegalStateException("No model maxContinuations found for model: " + modelName));
final var baseUrl = Optional.ofNullable(resource.getProperty("baseUrl"))
.map(Object::toString)
.orElseThrow(() -> new IllegalStateException("No model baseUrl found for model: " + modelName));
return new ModelProfile(name, maxTokens, maxContextLength, temperature, topP, maxContinuations, baseUrl);
}
catch (final IOException e) {
LOGGER.error("Error while reading model profile", e);
return null;
}
}
public List<String> getModelProfileUrls() {
final var modelProfiles = Binder.get(this.environment)
.bind(MODEL_PROFILE_PREFIX, Bindable.mapOf(String.class, String.class))
.orElse(Collections.emptyMap());
return modelProfiles.values().stream()
.filter(model -> model != null && !model.isEmpty())
.map(model -> {
try {
final var resource = new ResourcePropertySource("classpath:" + model + ".properties");
return resource.getProperty("baseUrl") != null ? resource.getProperty("baseUrl").toString() : null;
}
catch (final IOException e) {
LOGGER.error("Error while reading model profile for: " + model, e);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}

View File

@@ -1,46 +0,0 @@
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("</s>", "");
}
@Override
public String getPromptName() {
return "alpaca";
}
}

View File

@@ -1,45 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import org.springframework.stereotype.Component;
@Component
public class ChatMLPromptStrategy implements PromptStrategy {
@Override
public StringBuilder start(final String instruction, final String question, final String text) {
final StringBuilder prompt = new StringBuilder();
prompt.append("<|im_start|>system\n");
prompt.append(instruction);
prompt.append("<|im_end|>\n<|im_start|>user\n");
prompt.append(question);
if (!question.isEmpty() && !text.isEmpty()) {
prompt.append("\n");
}
prompt.append(text);
prompt.append("<|im_end|>\n<|im_start|>assistant\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("<|im_end|>\n<|im_start|>user\n");
prompt.append(user);
prompt.append("<|im_end|>\n<|im_start|>assistant\n");
}
@Override
public String removeEOT(final String prompt) {
return prompt.strip().replace("<|im_end|>", "");
}
@Override
public String getPromptName() {
return "chatml";
}
}

View File

@@ -1,48 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import org.springframework.stereotype.Component;
@Component
public class Gemma2PromptStrategy implements PromptStrategy {
@Override
public StringBuilder start(final String instruction, final String question, final String text) {
final StringBuilder prompt = new StringBuilder();
prompt.append("<start_of_turn>user\n");
if (!instruction.isEmpty()) {
prompt.append(instruction);
prompt.append("\n");
}
prompt.append(question);
if (!question.isEmpty() && !text.isEmpty()) {
prompt.append("\n");
}
prompt.append(text);
prompt.append("<end_of_turn>\n<start_of_turn>model\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("<end_of_turn>\n<start_of_turn>user\n");
prompt.append(user);
prompt.append("<end_of_turn>\n<start_of_turn>model\n");
}
@Override
public String removeEOT(final String prompt) {
return prompt.strip().replace("<end_of_turn>", "");
}
@Override
public String getPromptName() {
return "gemma2";
}
}

View File

@@ -1,47 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import org.springframework.stereotype.Component;
/**
* @see https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3
*/
@Component
public class Llama3PromptStrategy implements PromptStrategy {
@Override
public StringBuilder start(final String instruction, final String question, final String text) {
final StringBuilder prompt = new StringBuilder();
prompt.append("<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n");
prompt.append(instruction);
prompt.append("<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n");
prompt.append(question);
if (!question.isEmpty() && !text.isEmpty()) {
prompt.append("\n\n");
}
prompt.append(text);
prompt.append("<|eot_id|><|start_header_id|>assistant<|end_header_id|>\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("<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n" + user + "<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n");
}
@Override
public String removeEOT(final String prompt) {
return prompt.strip().replace("<|eot_id|>", "");
}
@Override
public String getPromptName() {
return "llama3";
}
}

View File

@@ -1,49 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import org.springframework.stereotype.Component;
@Component
public class MixtralPromptStrategy implements PromptStrategy {
@Override
public StringBuilder start(final String instruction, final String question, final String text) {
final StringBuilder prompt = new StringBuilder();
prompt.append("<s>[INST] ");
prompt.append(instruction);
if (!instruction.isEmpty() && !question.isEmpty()) {
prompt.append("\n");
}
prompt.append(question);
if ((!instruction.isEmpty() || !question.isEmpty()) && !text.isEmpty()) {
prompt.append("\n");
}
prompt.append(text);
prompt.append(" [/INST]");
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("\n\"");
prompt.append(assistant);
prompt.append("\"</s> [INST] ");
prompt.append(user);
prompt.append(" [/INST]");
}
@Override
public String removeEOT(final String prompt) {
return prompt.strip().replace("</s>", "");
}
@Override
public String getPromptName() {
return "mixtral";
}
}

View File

@@ -1,46 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import org.springframework.stereotype.Component;
@Component
public class Phi3PromptStrategy implements PromptStrategy {
@Override
public StringBuilder start(final String instruction, final String question, final String text) {
final StringBuilder prompt = new StringBuilder();
prompt.append("<|user|>\n");
prompt.append(instruction);
prompt.append(" ");
prompt.append(question);
if (!question.isEmpty() && !text.isEmpty()) {
prompt.append("\n");
}
prompt.append(text);
prompt.append(" <|end|>\n<|assistant|>");
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("\n");
prompt.append(assistant);
prompt.append("<|user|>\n");
prompt.append(user);
prompt.append("<|end|>\n<|assistant|>");
}
@Override
public String removeEOT(final String prompt) {
return prompt.strip().replace("<|end|>", "");
}
@Override
public String getPromptName() {
return "phi3";
}
}

View File

@@ -1,17 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
public interface PromptStrategy {
String getPromptName();
StringBuilder start(String instruction, String question, String text);
void continue_(StringBuilder prompt, String assistant);
void next(StringBuilder prompt, String assistant, String user);
default String removeEOT(final String prompt) {
return prompt.strip();
}
}

View File

@@ -1,22 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class PromptStrategyFactory {
private final Map<String, PromptStrategy> strategies = new HashMap<>();
public PromptStrategyFactory(final List<PromptStrategy> strategies) {
strategies.forEach(strategy -> this.strategies.put(strategy.getPromptName(), strategy));
}
public PromptStrategy getStrategy(final String modelPrompt) {
return this.strategies.get(modelPrompt);
}
}

View File

@@ -1,46 +0,0 @@
package de.extio.lmdatasetprep.client.prompt;
import org.springframework.stereotype.Component;
@Component
public class VicunaPromptStrategy implements PromptStrategy {
@Override
public StringBuilder start(final String instruction, final String question, final String text) {
final StringBuilder prompt = new StringBuilder();
prompt.append(instruction);
prompt.append(" USER: ");
prompt.append(question);
if (!question.isEmpty() && !text.isEmpty()) {
prompt.append("\n");
}
prompt.append(text);
prompt.append(" ASSISTANT:");
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(" ");
prompt.append(assistant);
prompt.append("</s>USER: ");
prompt.append(user);
prompt.append(" ASSISTANT:");
}
@Override
public String removeEOT(final String prompt) {
return prompt.strip().replace("</s>", "");
}
@Override
public String getPromptName() {
return "vicuna";
}
}

View File

@@ -0,0 +1,176 @@
package de.extio.lmdatasetprep.preparer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.extio.lmlib.client.ClientService;
import de.extio.lmlib.profile.ModelCategory;
abstract class AbstractContextualPrompts implements InitializingBean, Consumer<String[]> {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractContextualPrompts.class);
private static final byte[] NEWLINE = "\n".getBytes(StandardCharsets.UTF_8);
@Autowired
protected ClientService clientService;
private List<String> maleNames = new ArrayList<>();
private List<String> femaleNames = new ArrayList<>();
@Override
public void afterPropertiesSet() throws Exception {
this.maleNames = new BufferedReader(new InputStreamReader(new ClassPathResource("male.txt").getInputStream())).lines().collect(Collectors.toList());
this.femaleNames = new BufferedReader(new InputStreamReader(new ClassPathResource("female.txt").getInputStream())).lines().collect(Collectors.toList());
}
@Override
public void accept(final String[] args) {
if (args.length < 6) {
LOGGER.error("Arguments are missing. <Path to text files> <Path to output dir> <variations> [<chunk norm> <chunk var>, ...]");
return;
}
final int variations = Integer.parseInt(args[3]);
final List<ChunkCfg> 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);
}
Utils.transformDirectory(args[1], this.createTasks(args, chunkConfigurations, variations)::createTasks);
}
@FunctionalInterface
static interface CreateTasks {
List<Runnable> createTasks(Path path);
}
protected abstract CreateTasks createTasks(String[] args, List<ChunkCfg> chunkConfigurations, int variations);
protected List<String> fileToParagraphs(final Path file, final int chunkNorm, final int chunkVar) {
LOGGER.info("Splitting to jsonl " + file + " -> " + chunkNorm + "±" + chunkVar);
String text;
try {
text = Utils.normalizeText(Files.readString(file));
}
catch (final IOException e) {
throw new RuntimeException("Cannot read file", e);
}
return Utils.splitParagraphs(text, chunkNorm, chunkVar, false);
}
protected Map<String, String> createCharacterNameMapping(final String paragraph) {
final var completion = this.clientService.getClient(ModelCategory.COLD).completion(ModelCategory.COLD,
"You are a helpful assistant.",
"Extract all names and genders from the following text. Return the result in json format with the following fields: {\"males\" : [ \"Male name 1\", \"Male name 2\", … ], \"females\" : [ \"Female name 1\", \"Female name 2\", … ]}. Don't return a preamble and no explanation:",
paragraph);
final var response = completion.response();
final var start = response.indexOf('{');
final var end = response.lastIndexOf('}');
if (start == -1 || end == -1) {
return Map.of();
}
final var json = response.substring(start, end + 1);
Names names;
final ObjectMapper mapper = new ObjectMapper();
try {
names = mapper.readValue(json, Names.class);
}
catch (final JsonProcessingException e) {
return Map.of();
}
final Random random = ThreadLocalRandom.current();
final Map<String, String> mapping = new HashMap<>();
if (names.males != null) {
names.males.forEach(name -> mapping.put(name, this.maleNames.get(random.nextInt(this.maleNames.size()))));
}
if (names.females != null) {
names.females.forEach(name -> mapping.put(name, this.femaleNames.get(random.nextInt(this.femaleNames.size()))));
}
mapping.keySet().removeIf(key -> key.length() < 5);
if (mapping.isEmpty()) {
return Map.of();
}
LOGGER.debug(mapping.toString());
return mapping;
}
protected String renameCharacters(final String paragraph, final Map<String, String> mapping) {
var result = paragraph;
for (final var entry : mapping.entrySet()) {
result = result.replaceAll("(?i)" + entry.getKey(), entry.getValue());
}
return result;
}
protected String paragraphToContextualPrompt(final String paragraph, final String instruction) {
final var completion = this.clientService.getClient(ModelCategory.COLD).completion(ModelCategory.COLD,
"You are an assistant with great text writing skills.",
instruction,
paragraph);
String prompt = completion.response();
final int colon = prompt.indexOf(':');
if (colon > -1 && colon < prompt.length() + 1) {
prompt = prompt.substring(colon + 1);
}
final int preamble = prompt.indexOf("\n\n");
if (preamble > -1 && preamble < prompt.length() + 2) {
prompt = prompt.substring(preamble + 2);
}
prompt = prompt.trim();
return prompt;
}
protected void writeJsonLine(final ObjectMapper mapper, final OutputStream fos, final Object line) {
try {
final var jsonb = mapper
.writeValueAsString(line)
.getBytes(StandardCharsets.UTF_8);
fos.write(jsonb);
fos.write(NEWLINE);
}
catch (final IOException e) {
LOGGER.error("Cannot convert split to json", e);
}
}
protected record ChunkCfg(int chunksNorm, int chunksVar) {
}
protected record Names(List<String> males, List<String> females) {
}
}

View File

@@ -7,14 +7,13 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import de.extio.lmdatasetprep.XorShift128Random;
@Component @Component
public class MergeJsonl implements Consumer<String[]> { public class MergeJsonl implements Consumer<String[]> {
@@ -42,7 +41,7 @@ public class MergeJsonl implements Consumer<String[]> {
}); });
}); });
Collections.shuffle(lines, new XorShift128Random()); Collections.shuffle(lines, ThreadLocalRandom.current());
final Path out = Path.of(args[2], "dataset-merged.jsonl"); final Path out = Path.of(args[2], "dataset-merged.jsonl");
try (var fos = Files.newOutputStream(out)) { try (var fos = Files.newOutputStream(out)) {

View File

@@ -6,7 +6,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -15,17 +15,14 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import de.extio.lmdatasetprep.XorShift128Random; import de.extio.lmlib.client.ClientService;
import de.extio.lmdatasetprep.client.Client; import de.extio.lmlib.profile.ModelCategory;
import de.extio.lmdatasetprep.client.profile.ModelCategory;
@Component @Component
public class Rewrite implements Consumer<String[]> { public class Rewrite implements Consumer<String[]> {
private static final Logger LOGGER = LoggerFactory.getLogger(Rewrite.class); private static final Logger LOGGER = LoggerFactory.getLogger(Rewrite.class);
private static final ThreadLocal<Random> RANDOM = ThreadLocal.withInitial(XorShift128Random::new);
private static final byte[] PARAGRAPH = "\n\n".getBytes(StandardCharsets.UTF_8); 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 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:";
@@ -50,7 +47,7 @@ public class Rewrite implements Consumer<String[]> {
"Imbue the setting with a sense of timelessness or age through subtle phrasing and word choice"); "Imbue the setting with a sense of timelessness or age through subtle phrasing and word choice");
@Autowired @Autowired
private Client client; private ClientService clientService;
@Override @Override
public void accept(final String[] args) { public void accept(final String[] args) {
@@ -65,7 +62,7 @@ public class Rewrite implements Consumer<String[]> {
tasks.add(() -> this.rewriteFile(args, f, 0, "impr", ModelCategory.COLD, IMPROVEMENT_PROMPT)); tasks.add(() -> this.rewriteFile(args, f, 0, "impr", ModelCategory.COLD, IMPROVEMENT_PROMPT));
for (int i = 0; i < Integer.parseInt(args[3]); i++) { for (int i = 0; i < Integer.parseInt(args[3]); i++) {
final int fi = i; final int fi = i;
tasks.add(() -> this.rewriteFile(args, f, fi, "enh", ModelCategory.COLD, String.format(ENHANCE_PROMPT, ENHANCEMENTS.get(RANDOM.get().nextInt(ENHANCEMENTS.size()))))); tasks.add(() -> this.rewriteFile(args, f, fi, "enh", ModelCategory.COLD, String.format(ENHANCE_PROMPT, ENHANCEMENTS.get(ThreadLocalRandom.current().nextInt(ENHANCEMENTS.size())))));
} }
return tasks; return tasks;
}); });
@@ -116,10 +113,10 @@ public class Rewrite implements Consumer<String[]> {
for (final String split : splits) { for (final String split : splits) {
LOGGER.info("Split " + (splits.indexOf(split) + 1) + "/" + splits.size()); LOGGER.info("Split " + (splits.indexOf(split) + 1) + "/" + splits.size());
final var completion = this.client.completion("You are a helpful assistant with great authoring skills.", final var completion = this.clientService.getClient(modelCategory).completion(modelCategory,
"You are a helpful assistant with great authoring skills.",
prompt, prompt,
split, split);
modelCategory);
consumer.accept(completion.response()); consumer.accept(completion.response());
} }
} }

View File

@@ -1,67 +1,26 @@
package de.extio.lmdatasetprep.preparer; package de.extio.lmdatasetprep.preparer;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Consumer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import de.extio.lmdatasetprep.XorShift128Random;
import de.extio.lmdatasetprep.client.Client;
import de.extio.lmdatasetprep.client.profile.ModelCategory;
@Component @Component
public class Text2Jsonl2WithContextualPrompts implements Consumer<String[]>, InitializingBean { public class Text2Jsonl2WithContextualPrompts extends AbstractContextualPrompts {
private static final byte[] NEWLINE = "\n".getBytes(StandardCharsets.UTF_8);
private static final Logger LOGGER = LoggerFactory.getLogger(Text2Jsonl2WithContextualPrompts.class); private static final Logger LOGGER = LoggerFactory.getLogger(Text2Jsonl2WithContextualPrompts.class);
private List<String> maleNames = new ArrayList<>();
private List<String> femaleNames = new ArrayList<>();
@Autowired
private Client client;
@Override @Override
public void afterPropertiesSet() throws Exception { protected CreateTasks createTasks(final String[] args, final List<ChunkCfg> chunkConfigurations, final int variations) {
this.maleNames = Files.readAllLines(ResourceUtils.getFile("classpath:male.txt").toPath()); return p -> {
this.femaleNames = Files.readAllLines(ResourceUtils.getFile("classpath:female.txt").toPath()); final ObjectMapper mapper = new ObjectMapper();
}
@Override
public void accept(final String[] args) {
if (args.length < 6) {
LOGGER.error("Arguments are missing. <Path to text files> <Path to output dir> <variations> [<chunk norm> <chunk var>, ...]");
return;
}
final int variations = Integer.parseInt(args[3]);
final List<ChunkCfg> 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();
Utils.transformDirectory(args[1], p -> {
final List<Runnable> tasks = new ArrayList<>(); final List<Runnable> tasks = new ArrayList<>();
for (final ChunkCfg chunkCfg : chunkConfigurations) { for (final ChunkCfg chunkCfg : chunkConfigurations) {
@@ -108,112 +67,9 @@ public class Text2Jsonl2WithContextualPrompts implements Consumer<String[]>, Ini
} }
return tasks; return tasks;
}); };
} }
List<String> fileToParagraphs(final Path file, final int chunkNorm, final int chunkVar) { protected record QaLine(String question, String answer) {
LOGGER.info("Splitting to jsonl " + file + " -> " + chunkNorm + "±" + chunkVar);
String text;
try {
text = Utils.normalizeText(Files.readString(file));
}
catch (final IOException e) {
throw new RuntimeException("Cannot read file", e);
}
return Utils.splitParagraphs(text, chunkNorm, chunkVar, false);
} }
private Map<String, String> createCharacterNameMapping(final String paragraph) {
final var completion = this.client.completion("You are a helpful assistant.",
"Extract all names and genders from the following text. Return the result in json format with the following fields: {\"males\" : [ \"Male name 1\", \"Male name 2\", … ], \"females\" : [ \"Female name 1\", \"Female name 2\", … ]}. Don't return a preamble and no explanation:",
paragraph,
ModelCategory.COLD);
final var response = completion.response();
final var start = response.indexOf('{');
final var end = response.lastIndexOf('}');
if (start == -1 || end == -1) {
return Map.of();
}
final var json = response.substring(start, end + 1);
Names names;
final ObjectMapper mapper = new ObjectMapper();
try {
names = mapper.readValue(json, Names.class);
}
catch (final JsonProcessingException e) {
return Map.of();
}
final Random random = new XorShift128Random();
final Map<String, String> mapping = new HashMap<>();
if (names.males != null) {
names.males.forEach(name -> mapping.put(name, this.maleNames.get(random.nextInt(this.maleNames.size()))));
}
if (names.females != null) {
names.females.forEach(name -> mapping.put(name, this.femaleNames.get(random.nextInt(this.femaleNames.size()))));
}
mapping.keySet().removeIf(key -> key.length() < 5);
if (mapping.isEmpty()) {
return Map.of();
}
LOGGER.debug(mapping.toString());
return mapping;
}
private String renameCharacters(final String paragraph, final Map<String, String> mapping) {
var result = paragraph;
for (final var entry : mapping.entrySet()) {
result = result.replaceAll("(?i)" + entry.getKey(), entry.getValue());
}
return result;
}
private String paragraphToContextualPrompt(final String paragraph, final String instruction) {
final var completion = this.client.completion("You are an assistant with great text writing skills.",
instruction,
paragraph,
ModelCategory.COLD);
String prompt = completion.response();
final int colon = prompt.indexOf(':');
if (colon > -1 && colon < prompt.length() + 1) {
prompt = prompt.substring(colon + 1);
}
final int preamble = prompt.indexOf("\n\n");
if (preamble > -1 && preamble < prompt.length() + 2) {
prompt = prompt.substring(preamble + 2);
}
prompt = prompt.trim();
return prompt;
}
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) {
}
record Names(List<String> males, List<String> females) {
}
} }

View File

@@ -13,8 +13,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import de.extio.lmdatasetprep.client.Client; import de.extio.lmlib.client.ClientService;
import de.extio.lmdatasetprep.client.profile.ModelCategory; import de.extio.lmlib.profile.ModelCategory;
@Component @Component
public class Translate implements Consumer<String[]> { public class Translate implements Consumer<String[]> {
@@ -24,7 +24,7 @@ public class Translate implements Consumer<String[]> {
private static final byte[] PARAGRAPH = "\n\n".getBytes(StandardCharsets.UTF_8); private static final byte[] PARAGRAPH = "\n\n".getBytes(StandardCharsets.UTF_8);
@Autowired @Autowired
private Client client; private ClientService clientService;
@Override @Override
public void accept(final String[] args) { public void accept(final String[] args) {
@@ -77,10 +77,10 @@ public class Translate implements Consumer<String[]> {
for (final String split : splits) { for (final String split : splits) {
LOGGER.info("Split " + splits.indexOf(split) + "/" + splits.size()); 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.", final var completion = this.clientService.getClient(ModelCategory.COLD).completion(ModelCategory.COLD,
"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:", "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, split);
ModelCategory.COLD);
consumer.accept(completion.response()); consumer.accept(completion.response());
} }
} }

View File

@@ -17,14 +17,14 @@ import java.util.stream.Stream;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.extio.lmdatasetprep.LmDatasetPreparerApplication;
public class Utils { public class Utils {
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
private static final List<String> PARAGRAPH_DELIMITERS = List.of("\n\n", "\n", ".", "!", "?"); private static final List<String> PARAGRAPH_DELIMITERS = List.of("\n\n", "\n", ".", "!", "?");
private static final int EXECUTORS = 4;
static String normalizeText(final String text) { static String normalizeText(final String text) {
String result = text; String result = text;
@@ -127,8 +127,8 @@ public class Utils {
} }
static void transformDirectory(final String path, final Function<Path, List<Runnable>> createTasks) { static void transformDirectory(final String path, final Function<Path, List<Runnable>> createTasks) {
final int EXECUTORS = Integer.parseInt(LmDatasetPreparerApplication.applicationContext.getEnvironment().getProperty("agent.threads"));
final AtomicBoolean finished = new AtomicBoolean(false); final AtomicBoolean finished = new AtomicBoolean(false);
final BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>(EXECUTORS * 2); final BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>(EXECUTORS * 2);
final List<Thread> threads = new ArrayList<>(EXECUTORS); final List<Thread> threads = new ArrayList<>(EXECUTORS);

View File

@@ -2,7 +2,14 @@ spring.main.web-application-type=NONE
logging.level.de.extio=info logging.level.de.extio=info
profile.hot=llama3-hot profile.model.s=llama3-cold
profile.cold=llama3-cold profile.model.m=llama3-cold
profile.model.l=llama3-cold
profile.model.hot=llama3-hot
profile.model.cold=llama3-cold
agent.threads=4
client.collectStatistics=true client.collectStatistics=true
tokenizer.strategy=llamaServer

View File

@@ -1,7 +1,11 @@
prompts=llama3 prompts=llama3
tokenEncoding=
maxTokens=2000 maxTokens=2000
maxContextLength=4000 maxContextLength=4000
temperature=0.2 temperature=0.2
topP=1.0 topP=1.0
maxContinuations=1 maxContinuations=1
baseUrl=http://localhost:5001 modelProvider=OAI_TEXT_COMPLETION
modelName=
url=http://localhost:5001
apiKey=

View File

@@ -1,7 +1,11 @@
prompts=llama3 prompts=llama3
tokenEncoding=
maxTokens=2000 maxTokens=2000
maxContextLength=4000 maxContextLength=4000
temperature=0.3 temperature=0.4
topP=1.0 topP=1.0
maxContinuations=1 maxContinuations=1
baseUrl=http://localhost:5001 modelProvider=OAI_TEXT_COMPLETION
modelName=
url=http://localhost:5001
apiKey=

View File

@@ -0,0 +1,11 @@
prompts=mixtral
tokenEncoding=
maxTokens=2000
maxContextLength=4000
temperature=0.2
topP=1.0
maxContinuations=1
modelProvider=OAI_TEXT_COMPLETION
modelName=
url=http://localhost:5001
apiKey=

View File

@@ -1,145 +0,0 @@
package de.extio.lmdatasetprep.client;
import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import de.extio.lmdatasetprep.client.profile.ModelCategory;
@Disabled
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class ClientTest {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientTest.class);
@Autowired
private Client client;
@Test
void multiPrompts() throws Exception {
final var completion = this.client.completion(
"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.COLD);
LOGGER.info(completion.response());
}
@Test
void hugePrompt() throws Exception {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
sb.append(", ");
}
sb.delete(sb.length() - 2, sb.length());
final var completion = this.client.completion(
"You are counting machine",
"How many numbers do you count?",
sb.toString(),
ModelCategory.COLD);
LOGGER.info(completion.response());
}
@Test
void parallelRequests() throws Exception {
final LocalDateTime start = LocalDateTime.now();
final Tasks tasks = this.createTasks();
final ExecutorService executor = Executors.newFixedThreadPool(4);
try {
CompletableFuture.allOf(
CompletableFuture.runAsync(tasks.task1, executor),
CompletableFuture.runAsync(tasks.task2, executor),
CompletableFuture.runAsync(tasks.task3, executor),
CompletableFuture.runAsync(tasks.task4, executor))
.join();
}
finally {
executor.shutdown();
}
LOGGER.info("Duration: " + java.time.Duration.between(start, LocalDateTime.now()));
}
@Test
void serialRequests() throws Exception {
final LocalDateTime start = LocalDateTime.now();
final Tasks tasks = this.createTasks();
tasks.task1.run();
tasks.task2.run();
tasks.task3.run();
tasks.task4.run();
LOGGER.info("Duration: " + java.time.Duration.between(start, LocalDateTime.now()));
}
private Tasks createTasks() {
return new Tasks(() -> {
try {
final var completion = this.client.completion(
"You are a helpful assistant",
"",
"How do I calculate the annual profit margin?",
ModelCategory.COLD);
LOGGER.info(completion.response());
}
catch (final Exception e) {
LOGGER.error("Exception", e);
}
}, () -> {
try {
final var completion = this.client.completion(
"You are a helpful assistant",
"",
"How do I calculate the operating profit ratio?",
ModelCategory.COLD);
LOGGER.info(completion.response());
}
catch (final Exception e) {
LOGGER.error("Exception", e);
}
}, () -> {
try {
final var completion = this.client.completion(
"You are a helpful assistant",
"",
"How do I calculate the return of investment?",
ModelCategory.COLD);
LOGGER.info(completion.response());
}
catch (final Exception e) {
LOGGER.error("Exception", e);
}
}, () -> {
try {
final var completion = this.client.completion(
"You are a helpful assistant",
"",
"How do I calculate the return on net worth?",
ModelCategory.COLD);
LOGGER.info(completion.response());
}
catch (final Exception e) {
LOGGER.error("Exception", e);
}
});
}
static record Tasks(Runnable task1, Runnable task2, Runnable task3, Runnable task4) {
}
}

View File

@@ -1,4 +0,0 @@
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.max-pool-size=2
junit.jupiter.execution.parallel.config.fixed.parallelism=2