cn.hutool
hutool-system
diff --git a/smartjavaai-translate/pom.xml b/smartjavaai-translate/pom.xml
new file mode 100644
index 0000000..9095fe3
--- /dev/null
+++ b/smartjavaai-translate/pom.xml
@@ -0,0 +1,149 @@
+
+
+ 4.0.0
+
+ cn.smartjavaai
+ smartjavaai-parent
+ 1.0.13
+
+
+ smartjavaai-translate
+
+
+
+
+ UTF-8
+ 1.5.8
+ 5.1.2-1.5.8
+
+
+
+
+ cn.smartjavaai
+ smartjavaai-common
+ ${project.version}
+
+
+ org.bytedeco
+ javacpp
+ ${javacv.version}
+
+
+ org.bytedeco
+ ffmpeg
+ ${javacv.ffmpeg.version}
+
+
+ ai.djl.opencv
+ opencv
+
+
+
+ 1.0.13
+ smartjavaai-ocr
+ SmartJavaAI
+ https://github.com/geekwenjie/SmartJavaAI
+
+
+ MIT License
+ https://opensource.org/licenses/MIT
+
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.4.0
+ true
+
+ dengwenjie
+ true
+ ${project.groupId}:${project.artifactId}:${project.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.1.0
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.1.0
+
+
+ none
+
+ -Xdoclint:none
+
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.1.0
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+ scm:git:git://github.com/geekwenjie/SmartJavaAI.git
+ scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git
+ http://github.com/geekwenjie/SmartJavaAI/tree/master
+
+
+
+
+
+ dengwenjie
+ https://s01.oss.sonatype.org/content/repositories/snapshots
+
+
+ dengwenjie
+ https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+
+ dengwenjie
+ 775747758@qq.com
+
+ Project Manager
+ Architect
+
+
+
+
+
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/TextTranslation.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/TextTranslation.java
new file mode 100644
index 0000000..c86080b
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/TextTranslation.java
@@ -0,0 +1,57 @@
+package cn.smartjavaai.translate;
+
+import ai.djl.Device;
+import ai.djl.ModelException;
+import ai.djl.translate.TranslateException;
+import cn.smartjavaai.translate.generate.SearchConfig;
+import cn.smartjavaai.translate.model.NllbModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * 文本翻译,支持202种语言互译
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public final class TextTranslation {
+
+ private static final Logger logger = LoggerFactory.getLogger(TextTranslation.class);
+
+ private TextTranslation() {
+ }
+
+ public static void main(String[] args) throws ModelException, IOException,
+ TranslateException {
+
+ SearchConfig config = new SearchConfig();
+ // 设置输出文字的最大长度
+ config.setMaxSeqLength(128);
+ // 设置源语言:中文 "zho_Hans": 256200
+ config.setSrcLangId(256200);
+ // 设置目标语言:英文 "eng_Latn": 256047
+ config.setForcedBosTokenId(256047);
+ config.setForcedBosTokenId(256201);
+
+ // 输入文字
+ String input = "智利北部的丘基卡马塔矿是世界上最大的露天矿之一,长约4公里,宽3公里,深1公里。";
+
+ String modelPath = "E:\\ai\\models\\nlp\\";
+ String cpuModelName = "traced_translation_cpu.pt";
+ String gpuModelName = "traced_translation_gpu.pt";
+ try (NllbModel nllbModel = new NllbModel(config, modelPath, cpuModelName, Device.cpu())) {
+
+ System.setProperty("ai.djl.pytorch.graph_optimizer", "false");
+
+ // 运行模型,获取翻译结果
+ String result = nllbModel.translate(input);
+
+ logger.info("result========={}", result);
+ } finally {
+ System.clearProperty("ai.djl.pytorch.graph_optimizer");
+ }
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/BatchTensorList.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/BatchTensorList.java
new file mode 100644
index 0000000..51ebfc5
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/BatchTensorList.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package cn.smartjavaai.translate.generate;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+
+/**
+ * BatchTensorList represents a search state, and the NDArrays inside are updated in each iteration
+ * of the autoregressive loop.
+ *
+ * It is a struct consisting of NDArrays, whose first dimension is batch, and also contains
+ * sequence dimension (whose position in tensor's shape is specified by seqDimOrder). The SeqBatcher
+ * batch operations will operate on these two dimensions.
+ */
+public abstract class BatchTensorList {
+ // [batch, seq_past]. seq-dim-size == |past_seq| + |inputIds|. Will grow.
+ private NDArray pastOutputIds;
+
+ // [batch, seq_past]
+ // The cache of past attentionMask. seq-dim-size == |past_seq| + |inputIds|. Will grow.
+ private NDArray pastAttentionMask;
+
+ // (k, v) * numLayer,
+ // kv: [batch, heads, seq_past, kvfeature]
+ // The cache of past sequence. seq-dim-size == |past_seq| + |inputIds|. Will grow.
+ private NDList pastKeyValues;
+
+ // Sequence dimension order among all dimensions for each element in the batch list.
+ private long[] seqDimOrder;
+
+ BatchTensorList() {}
+
+ /**
+ * Constructs a new {@code BatchTensorList} instance.
+ *
+ * @param list the NDList that contains the serialized version of the batch tensors
+ * @param seqDimOrder the sequence dimension order that specifies where the sequence dimension
+ * is in a tensor's shape
+ */
+ BatchTensorList(NDList list, long[] seqDimOrder) {
+ this.seqDimOrder = seqDimOrder;
+ pastOutputIds = list.get(0);
+ pastAttentionMask = list.get(1);
+ pastKeyValues = list.subNDList(2);
+ }
+
+ /**
+ * Constructs a new {@code BatchTensorList} instance.
+ *
+ * @param pastOutputIds past output token ids
+ * @param pastAttentionMask past attention mask
+ * @param pastKeyValues past kv cache
+ * @param seqDimOrder the sequence dimension order that specifies where the sequence dimension
+ * is in a tensor's shape
+ */
+ BatchTensorList(
+ NDArray pastOutputIds,
+ NDArray pastAttentionMask,
+ NDList pastKeyValues,
+ long[] seqDimOrder) {
+ this.pastKeyValues = pastKeyValues;
+ this.pastOutputIds = pastOutputIds;
+ this.pastAttentionMask = pastAttentionMask;
+ this.seqDimOrder = seqDimOrder;
+ }
+
+ /**
+ * Constructs a new {@code BatchTensorList} instance from the serialized version of the batch
+ * tensors.
+ *
+ *
The pastOutputIds has to be the first in the output list.
+ *
+ * @param inputList the serialized version of the batch tensors
+ * @param seqDimOrder the sequence dimension order that specifies where the sequence dimension
+ * is in a tensor's shape
+ * @return BatchTensorList
+ */
+ public abstract BatchTensorList fromList(NDList inputList, long[] seqDimOrder);
+
+ /**
+ * Returns the serialized version of the BatchTensorList. The pastOutputIds has to be the first
+ * in the output list.
+ *
+ * @return the NDList that contains the serialized BatchTensorList
+ */
+ public abstract NDList getList();
+
+ /**
+ * Returns the sequence dimension order which specifies where the sequence dimension is in a
+ * tensor's shape.
+ *
+ * @return the sequence dimension order which specifies where the sequence dimension is in a
+ * tensor's shape
+ */
+ public long[] getSeqDimOrder() {
+ return seqDimOrder;
+ }
+
+ /**
+ * Returns the value of the pastOutputIds.
+ *
+ * @return the value of pastOutputIds
+ */
+ public NDArray getPastOutputIds() {
+ return pastOutputIds;
+ }
+
+ /**
+ * Sets the past output token ids.
+ *
+ * @param pastOutputIds the past output token ids
+ */
+ public void setPastOutputIds(NDArray pastOutputIds) {
+ this.pastOutputIds = pastOutputIds;
+ }
+
+ /**
+ * Returns the value of the pastAttentionMask.
+ *
+ * @return the value of pastAttentionMask
+ */
+ public NDArray getPastAttentionMask() {
+ return pastAttentionMask;
+ }
+
+ /**
+ * Sets the attention mask.
+ *
+ * @param pastAttentionMask the attention mask
+ */
+ public void setPastAttentionMask(NDArray pastAttentionMask) {
+ this.pastAttentionMask = pastAttentionMask;
+ }
+
+ /**
+ * Returns the value of the pastKeyValues.
+ *
+ * @return the value of pastKeyValues
+ */
+ public NDList getPastKeyValues() {
+ return pastKeyValues;
+ }
+
+ /**
+ * Sets the kv cache.
+ *
+ * @param pastKeyValues the kv cache
+ */
+ public void setPastKeyValues(NDList pastKeyValues) {
+ this.pastKeyValues = pastKeyValues;
+ }
+
+ /**
+ * Sets the sequence dimension order which specifies where the sequence dimension is in a
+ * tensor's shape.
+ *
+ * @param seqDimOrder the sequence dimension order which specifies where the sequence dimension
+ * is in a tensor's shape
+ */
+ public void setSeqDimOrder(long[] seqDimOrder) {
+ this.seqDimOrder = seqDimOrder;
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/CausalLMOutput.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/CausalLMOutput.java
new file mode 100644
index 0000000..62ce2c9
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/CausalLMOutput.java
@@ -0,0 +1,32 @@
+package cn.smartjavaai.translate.generate;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+/**
+ * 解码输出对象
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class CausalLMOutput {
+ private NDArray logits;
+ private NDList pastKeyValuesList;
+
+ public CausalLMOutput(NDArray logits, NDList pastKeyValues) {
+ this.logits = logits;
+ this.pastKeyValuesList = pastKeyValues;
+ }
+
+ public NDArray getLogits() {
+ return logits;
+ }
+
+ public void setLogits(NDArray logits) {
+ this.logits = logits;
+ }
+
+ public NDList getPastKeyValuesList() {
+ return pastKeyValuesList;
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/GreedyBatchTensorList.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/GreedyBatchTensorList.java
new file mode 100644
index 0000000..5b07476
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/GreedyBatchTensorList.java
@@ -0,0 +1,84 @@
+package cn.smartjavaai.translate.generate;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+
+/**
+ * 贪婪搜索张量对象列表
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class GreedyBatchTensorList extends BatchTensorList {
+ // [batch, 1]
+ private NDArray nextInputIds;
+
+ private NDArray pastOutputIds;
+
+ private NDArray encoderHiddenStates;
+ private NDArray attentionMask;
+ private NDList pastKeyValues;
+
+ public GreedyBatchTensorList(
+ NDArray nextInputIds,
+ NDArray pastOutputIds,
+ NDList pastKeyValues,
+ NDArray encoderHiddenStates,
+ NDArray attentionMask) {
+ this.nextInputIds = nextInputIds;
+ this.pastKeyValues = pastKeyValues;
+ this.pastOutputIds = pastOutputIds;
+ this.attentionMask = attentionMask;
+ this.encoderHiddenStates = encoderHiddenStates;
+ }
+
+ public GreedyBatchTensorList() {}
+
+ public BatchTensorList fromList(NDList inputList, long[] seqDimOrder) {
+ return new GreedyBatchTensorList();
+ }
+
+ public NDList getList() {
+ return new NDList();
+ }
+
+ public NDArray getNextInputIds() {
+ return nextInputIds;
+ }
+
+ public void setNextInputIds(NDArray nextInputIds) {
+ this.nextInputIds = nextInputIds;
+ }
+ public NDArray getPastOutputIds() {
+ return pastOutputIds;
+ }
+
+ public void setPastOutputIds(NDArray pastOutputIds) {
+ this.pastOutputIds = pastOutputIds;
+ }
+
+ public NDList getPastKeyValues() {
+ return pastKeyValues;
+ }
+
+ public void setPastKeyValues(NDList pastKeyValues) {
+ this.pastKeyValues = pastKeyValues;
+ }
+
+ public NDArray getEncoderHiddenStates() {
+ return encoderHiddenStates;
+ }
+
+ public void setEncoderHiddenStates(NDArray encoderHiddenStates) {
+ this.encoderHiddenStates = encoderHiddenStates;
+ }
+
+ public NDArray getAttentionMask() {
+ return attentionMask;
+ }
+
+ public void setAttentionMask(NDArray attentionMask) {
+ this.attentionMask = attentionMask;
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/SearchConfig.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/SearchConfig.java
new file mode 100644
index 0000000..50d873f
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/generate/SearchConfig.java
@@ -0,0 +1,104 @@
+package cn.smartjavaai.translate.generate;
+/**
+ * 配置信息
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class SearchConfig {
+
+ private int maxSeqLength;
+ private long padTokenId;
+ private long eosTokenId;
+ private long bosTokenId;
+ private long decoderStartTokenId;
+ private float encoderRepetitionPenalty;
+ private long forcedBosTokenId;
+ private long srcLangId;
+ private float lengthPenalty;
+ public SearchConfig() {
+ this.maxSeqLength = 512;
+ this.eosTokenId = 2;
+ this.bosTokenId = 0;
+ this.padTokenId = 1;
+ this.decoderStartTokenId = 2;
+ this.encoderRepetitionPenalty = 1.0f;
+ this.srcLangId = 0;
+ this.forcedBosTokenId = 0;
+ this.lengthPenalty = 1.0f;
+
+ }
+
+ public long getSrcLangId() {
+ return srcLangId;
+ }
+
+ public void setSrcLangId(long srcLangId) {
+ this.srcLangId = srcLangId;
+ }
+
+ public void setEosTokenId(long eosTokenId) {
+ this.eosTokenId = eosTokenId;
+ }
+
+ public int getMaxSeqLength() {
+ return maxSeqLength;
+ }
+
+ public void setMaxSeqLength(int maxSeqLength) {
+ this.maxSeqLength = maxSeqLength;
+ }
+
+ public long getPadTokenId() {
+ return padTokenId;
+ }
+
+ public void setPadTokenId(long padTokenId) {
+ this.padTokenId = padTokenId;
+ }
+
+ public long getEosTokenId() {
+ return eosTokenId;
+ }
+
+ public long getDecoderStartTokenId() {
+ return decoderStartTokenId;
+ }
+
+ public void setDecoderStartTokenId(long decoderStartTokenId) {
+ this.decoderStartTokenId = decoderStartTokenId;
+ }
+
+ public float getEncoderRepetitionPenalty() {
+ return encoderRepetitionPenalty;
+ }
+
+ public void setEncoderRepetitionPenalty(float encoderRepetitionPenalty) {
+ this.encoderRepetitionPenalty = encoderRepetitionPenalty;
+ }
+
+ public long getForcedBosTokenId() {
+ return forcedBosTokenId;
+ }
+
+ public void setForcedBosTokenId(long forcedBosTokenId) {
+ this.forcedBosTokenId = forcedBosTokenId;
+ }
+
+ public float getLengthPenalty() {
+ return lengthPenalty;
+ }
+
+ public void setLengthPenalty(float lengthPenalty) {
+ this.lengthPenalty = lengthPenalty;
+ }
+
+ public long getBosTokenId() {
+ return bosTokenId;
+ }
+
+ public void setBosTokenId(long bosTokenId) {
+ this.bosTokenId = bosTokenId;
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/Decoder2Translator.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/Decoder2Translator.java
new file mode 100644
index 0000000..f3a4f9c
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/Decoder2Translator.java
@@ -0,0 +1,45 @@
+package cn.smartjavaai.translate.model;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.translate.NoBatchifyTranslator;
+import ai.djl.translate.TranslatorContext;
+import cn.smartjavaai.translate.generate.CausalLMOutput;
+
+/**
+ * 解碼器,參數支持 pastKeyValues
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class Decoder2Translator implements NoBatchifyTranslator {
+ private String tupleName;
+
+ public Decoder2Translator() {
+ tupleName = "past_key_values(" + 12 + ',' + 4 + ')';
+ }
+
+ @Override
+ public NDList processInput(TranslatorContext ctx, NDList input) {
+
+ NDArray placeholder = ctx.getNDManager().create(0);
+ placeholder.setName("module_method:decoder2");
+
+ input.add(placeholder);
+
+ return input;
+ }
+
+ @Override
+ public CausalLMOutput processOutput(TranslatorContext ctx, NDList output) {
+ NDArray logitsOutput = output.get(0);
+ NDList pastKeyValuesOutput = output.subNDList(1, 12 * 4 + 1);
+
+ for (NDArray array : pastKeyValuesOutput) {
+ array.setName(tupleName);
+ }
+
+ return new CausalLMOutput(logitsOutput, pastKeyValuesOutput);
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/DecoderTranslator.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/DecoderTranslator.java
new file mode 100644
index 0000000..34bdf94
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/DecoderTranslator.java
@@ -0,0 +1,44 @@
+package cn.smartjavaai.translate.model;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.translate.NoBatchifyTranslator;
+import ai.djl.translate.TranslatorContext;
+import cn.smartjavaai.translate.generate.CausalLMOutput;
+/**
+ * 解碼器,參數沒有 pastKeyValues
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class DecoderTranslator implements NoBatchifyTranslator {
+ private String tupleName;
+
+ public DecoderTranslator() {
+ tupleName = "past_key_values(" + 12 + ',' + 4 + ')';
+ }
+
+ @Override
+ public NDList processInput(TranslatorContext ctx, NDList input) {
+
+ NDArray placeholder = ctx.getNDManager().create(0);
+ placeholder.setName("module_method:decoder");
+
+ input.add(placeholder);
+
+ return input;
+ }
+
+ @Override
+ public CausalLMOutput processOutput(TranslatorContext ctx, NDList output) {
+ NDArray logitsOutput = output.get(0);
+ NDList pastKeyValuesOutput = output.subNDList(1, 12 * 4 + 1);
+
+ for (NDArray array : pastKeyValuesOutput) {
+ array.setName(tupleName);
+ }
+
+ return new CausalLMOutput(logitsOutput, pastKeyValuesOutput);
+ }
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/EncoderTranslator.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/EncoderTranslator.java
new file mode 100644
index 0000000..53ef90d
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/EncoderTranslator.java
@@ -0,0 +1,49 @@
+package cn.smartjavaai.translate.model;
+
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.translate.NoBatchifyTranslator;
+import ai.djl.translate.TranslatorContext;
+
+import java.util.Arrays;
+
+/**
+ * 编码器前后处理
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class EncoderTranslator implements NoBatchifyTranslator {
+
+
+ public EncoderTranslator() {
+ }
+
+ @Override
+ public NDList processInput(TranslatorContext ctx, long[] input) throws Exception {
+ NDManager manager = ctx.getNDManager();
+
+ NDArray inputIdArray = manager.create(input).expandDims(0);
+ inputIdArray.setName("input_ids");
+
+ long[] attentionMask = new long[input.length];
+ Arrays.fill(attentionMask, 1);
+ NDArray attentionMaskArray = manager.create(attentionMask).expandDims(0);
+ attentionMaskArray.setName("attention_mask");
+
+ NDArray placeholder = ctx.getNDManager().create(0);
+ placeholder.setName("module_method:encoder");
+
+ return new NDList(inputIdArray, attentionMaskArray, placeholder);
+ }
+
+ @Override
+ public NDArray processOutput(TranslatorContext ctx, NDList list) {
+ NDArray encoderHiddenStates = list.get(0);
+ encoderHiddenStates.detach();
+ return encoderHiddenStates;
+ }
+
+}
\ No newline at end of file
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/NllbModel.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/NllbModel.java
new file mode 100644
index 0000000..9b6b339
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/model/NllbModel.java
@@ -0,0 +1,183 @@
+package cn.smartjavaai.translate.model;
+
+import ai.djl.Device;
+import ai.djl.ModelException;
+import ai.djl.huggingface.tokenizers.Encoding;
+import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
+import ai.djl.inference.Predictor;
+import ai.djl.ndarray.NDArray;
+import ai.djl.ndarray.NDList;
+import ai.djl.ndarray.NDManager;
+import ai.djl.ndarray.index.NDIndex;
+import ai.djl.repository.zoo.Criteria;
+import ai.djl.repository.zoo.ZooModel;
+import ai.djl.translate.NoopTranslator;
+import ai.djl.translate.TranslateException;
+import cn.smartjavaai.translate.generate.CausalLMOutput;
+import cn.smartjavaai.translate.generate.GreedyBatchTensorList;
+import cn.smartjavaai.translate.generate.SearchConfig;
+import cn.smartjavaai.translate.tokenizer.TokenUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+/**
+ * 模型载入及推理
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public class NllbModel implements AutoCloseable {
+ private static final Logger logger = LoggerFactory.getLogger(NllbModel.class);
+ private SearchConfig config;
+ private ZooModel nllbModel;
+ private HuggingFaceTokenizer tokenizer;
+ private Predictor encoderPredictor;
+ private Predictor decoderPredictor;
+ private Predictor decoder2Predictor;
+ private NDManager manager;
+
+ public NllbModel(SearchConfig config, String modelPath, String modelName, Device device) throws ModelException, IOException {
+ this.config = config;
+ Criteria criteria =
+ Criteria.builder()
+ .setTypes(NDList.class, NDList.class)
+ .optModelPath(Paths.get(modelPath + modelName))
+ .optEngine("PyTorch")
+ .optDevice(device)
+ .optTranslator(new NoopTranslator())
+ .build();
+
+ manager = NDManager.newBaseManager(device);
+ nllbModel = criteria.loadModel();
+ tokenizer = HuggingFaceTokenizer.newInstance(Paths.get(modelPath + "tokenizer.json"));
+ encoderPredictor = nllbModel.newPredictor(new EncoderTranslator());
+ decoderPredictor = nllbModel.newPredictor(new DecoderTranslator());
+ decoder2Predictor = nllbModel.newPredictor(new Decoder2Translator());
+ }
+
+ public NDArray encoder(long[] ids) throws TranslateException {
+ return encoderPredictor.predict(ids);
+ }
+
+ public CausalLMOutput decoder(NDList input) throws TranslateException {
+ return decoderPredictor.predict(input);
+ }
+
+ public CausalLMOutput decoder2(NDList input) throws TranslateException {
+ return decoder2Predictor.predict(input);
+ }
+
+ @Override
+ public void close() {
+ encoderPredictor.close();
+ decoderPredictor.close();
+ decoder2Predictor.close();
+ nllbModel.close();
+ manager.close();
+ tokenizer.close();
+ }
+
+ public String translate(String input) throws TranslateException {
+
+ Encoding encoding = tokenizer.encode(input);
+ long[] ids = encoding.getIds();
+ // 1. Encoder
+ long[] inputIds = new long[ids.length];
+ // 设置源语言编码
+ inputIds[0] = config.getSrcLangId();
+ for (int i = 0; i < ids.length - 1; i++) {
+ inputIds[i + 1] = ids[i];
+ }
+ logger.info("inputIds: " + Arrays.toString(inputIds));
+ long[] attentionMask = encoding.getAttentionMask();
+ NDArray attentionMaskArray = manager.create(attentionMask).expandDims(0);
+
+ NDArray encoderHiddenStates = encoder(inputIds);
+
+ NDArray decoder_input_ids = manager.create(new long[]{config.getDecoderStartTokenId()}).reshape(1, 1);
+ NDList decoderInput = new NDList(decoder_input_ids, encoderHiddenStates, attentionMaskArray);
+
+ // 2. Initial Decoder
+ CausalLMOutput modelOutput = decoder(decoderInput);
+ modelOutput.getLogits().attach(manager);
+ modelOutput.getPastKeyValuesList().attach(manager);
+
+ GreedyBatchTensorList searchState =
+ new GreedyBatchTensorList(null, decoder_input_ids, modelOutput.getPastKeyValuesList(), encoderHiddenStates, attentionMaskArray);
+
+ while (true) {
+// try (NDScope ignore = new NDScope()) {
+ NDArray pastOutputIds = searchState.getPastOutputIds();
+
+ if (searchState.getNextInputIds() != null) {
+ decoderInput = new NDList(searchState.getNextInputIds(), searchState.getEncoderHiddenStates(), searchState.getAttentionMask());
+ decoderInput.addAll(searchState.getPastKeyValues());
+ // 3. Decoder loop
+ modelOutput = decoder2(decoderInput);
+ }
+
+ NDArray outputIds = greedyStepGen(config, pastOutputIds, modelOutput.getLogits());
+
+ searchState.setNextInputIds(outputIds);
+ pastOutputIds = pastOutputIds.concat(outputIds, 1);
+ searchState.setPastOutputIds(pastOutputIds);
+
+ searchState.setPastKeyValues(modelOutput.getPastKeyValuesList());
+
+ // memory management
+// NDScope.unregister(outputIds, pastOutputIds);
+// }
+
+ // Termination Criteria
+ long id = searchState.getNextInputIds().toLongArray()[0];
+ if (config.getEosTokenId() == id) {
+ searchState.setNextInputIds(null);
+ break;
+ }
+ if (searchState.getPastOutputIds() != null && searchState.getPastOutputIds().getShape().get(1) + 1 >= config.getMaxSeqLength()) {
+ break;
+ }
+ }
+
+ if (searchState.getNextInputIds() == null) {
+ NDArray resultIds = searchState.getPastOutputIds();
+ String result = TokenUtils.decode(config, tokenizer, resultIds);
+ return result;
+ } else {
+ NDArray resultIds = searchState.getPastOutputIds(); // .concat(searchState.getNextInputIds(), 1)
+ String result = TokenUtils.decode(config, tokenizer, resultIds);
+ return result;
+ }
+
+ }
+
+ public NDArray greedyStepGen(SearchConfig config, NDArray pastOutputIds, NDArray next_token_scores) {
+ next_token_scores = next_token_scores.get(":, -1, :");
+
+ NDArray new_next_token_scores = manager.create(next_token_scores.getShape(), next_token_scores.getDataType());
+ next_token_scores.copyTo(new_next_token_scores);
+
+ // LogitsProcessor 1. ForcedBOSTokenLogitsProcessor
+ // 设置目标语言
+ long cur_len = pastOutputIds.getShape().getLastDimension();
+ if (cur_len == 1) {
+ long num_tokens = new_next_token_scores.getShape().getLastDimension();
+ for (long i = 0; i < num_tokens; i++) {
+ if (i != config.getForcedBosTokenId()) {
+ new_next_token_scores.set(new NDIndex(":," + i), Float.NEGATIVE_INFINITY);
+ }
+ }
+ new_next_token_scores.set(new NDIndex(":," + config.getForcedBosTokenId()), 0);
+ }
+
+ NDArray probs = new_next_token_scores.softmax(-1);
+ NDArray next_tokens = probs.argMax(-1);
+
+ return next_tokens.expandDims(0);
+ }
+
+}
diff --git a/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/tokenizer/TokenUtils.java b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/tokenizer/TokenUtils.java
new file mode 100644
index 0000000..f4c0ab9
--- /dev/null
+++ b/smartjavaai-translate/src/main/java/cn/smartjavaai/translate/tokenizer/TokenUtils.java
@@ -0,0 +1,48 @@
+package cn.smartjavaai.translate.tokenizer;
+
+import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
+import ai.djl.ndarray.NDArray;
+import cn.smartjavaai.translate.generate.SearchConfig;
+
+import java.util.ArrayList;
+
+/**
+ * Token工具类
+ *
+ * @author Calvin
+ * @mail 179209347@qq.com
+ * @website www.aias.top
+ */
+public final class TokenUtils {
+
+ private TokenUtils() {
+ }
+
+ /**
+ * 语言解码
+ *
+ * @param tokenizer
+ * @param output
+ * @return
+ */
+ public static String decode(SearchConfig config, HuggingFaceTokenizer tokenizer, NDArray output) {
+ long[] outputIds = output.toLongArray();
+ ArrayList outputIdsList = new ArrayList<>();
+
+ for (long id : outputIds) {
+ if (id == config.getEosTokenId() || id==config.getSrcLangId() || id==config.getForcedBosTokenId()) {
+ continue;
+ }
+ outputIdsList.add(id);
+ }
+
+ Long[] objArr = outputIdsList.toArray(new Long[0]);
+ long[] ids = new long[objArr.length];
+ for (int i = 0; i < objArr.length; i++) {
+ ids[i] = objArr[i];
+ }
+ String text = tokenizer.decode(ids);
+
+ return text;
+ }
+}
\ No newline at end of file
diff --git "a/smartjavaai-translate/\350\257\255\350\250\200\347\274\226\347\240\201.xlsx" "b/smartjavaai-translate/\350\257\255\350\250\200\347\274\226\347\240\201.xlsx"
new file mode 100644
index 0000000000000000000000000000000000000000..d3a879f88f3d2df0f467b769dbb7a12d0720c684
GIT binary patch
literal 15590
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
literal 0
HcmV?d00001
--
Gitee