Instructions to use menezesbruno/manaca-1b-instruct with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use menezesbruno/manaca-1b-instruct with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="menezesbruno/manaca-1b-instruct")# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("menezesbruno/manaca-1b-instruct") model = AutoModelForCausalLM.from_pretrained("menezesbruno/manaca-1b-instruct", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use menezesbruno/manaca-1b-instruct with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "menezesbruno/manaca-1b-instruct" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "menezesbruno/manaca-1b-instruct", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/menezesbruno/manaca-1b-instruct
- SGLang
How to use menezesbruno/manaca-1b-instruct with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "menezesbruno/manaca-1b-instruct" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "menezesbruno/manaca-1b-instruct", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "menezesbruno/manaca-1b-instruct" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "menezesbruno/manaca-1b-instruct", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use menezesbruno/manaca-1b-instruct with Docker Model Runner:
docker model run hf.co/menezesbruno/manaca-1b-instruct
Rodando via llama.cpp (bug de tokenização)
Nota: Quando eu ia publicar isso, vi que sulfierry/manaca-1b-instruct-GGUF já tinha documentado uma conversão GGUF corrigida, chegando à mesma causa raiz que eu (SPM vs UGM). Publico mesmo assim porque os achados são complementares: ele foi além e mapeou um segundo problema ligado (
\nsendo tratado diferente entretokenizer.jsonetokenizer.model), que eu não tinha identificado. Recomendo ler os dois.
Rodei este modelo via llama.cpp/Open WebUI e encontrei um bug de tokenização na conversão pra GGUF. Deixando documentado aqui, com evidências, caso ajude alguém.
Sintomas observados
Convertendo com convert_hf_to_gguf.py (fluxo padrão), o GGUF resultante apresenta comportamento inconsistente:
- Prompts com maiúscula em qualquer posição (
"Qual","Explique", ou até no meio da frase como"a Capital") geram respostas piores ou incoerentes comparado ao mesmo prompt em minúsculas. - Em alguns casos especificamente maiúscula combinada com
?no final o modelo trava completamente, gerando espaço em branco em loop infinito, sem nunca parar sozinho.
Isso acontece de forma consistente e reproduzível.
Exemplo reproduzível:
$ ./llama-cli -m manaca-1b-instruct.f16.gguf --temp 0 -p "Qual a capital do brasil?"
Qual a capital do brasil?
[trava aqui só gera espaço em branco, indefinidamente]
$ ./llama-cli -m manaca-1b-instruct.f16.gguf --temp 0 -p "qual a capital do brasil?"
qual a capital do brasil? brasília? rio de janeiro? são paulo? não, a capital do país do futebol é a cidade...
[gera normalmente]
Diagnóstico
Passo 1 - Comparar a tokenização entre o modelo original (Python) e o GGUF.
Usando o mesmo texto ("abaixo está uma instrução"), via transformers:
>>> tok("abaixo está uma instrução")["input_ids"]
[1, 884, 340, 295, 2872]
Via llama.cpp (--verbose-prompt):
tokens: [ '<s>':1, ' abaixo':884, ' está':340, ' uma':295, ' in':531, 'stru':13115, 'ção':1370 ]
A palavra "instrução" é um único token (2872) no modelo original. No llama.cpp ela vira três tokens (' in', 'stru', 'ção'). A tokenização diverge do modelo real, mesmo com o tokenizer.model correto presente.
Passo 2 - Checar qual algoritmo de tokenizador o llama.cpp carregou.
$ ./llama-cli -m manaca-1b-instruct.f16.gguf --verbose-prompt -p "teste" 2>&1 | grep -E "tokenizer.ggml.model|init_tokenizer"
tokenizer.ggml.model str = llama
init_tokenizer: initializing tokenizer for type 1
No cabeçalho oficial do llama.cpp (include/llama.h):
enum llama_vocab_type {
LLAMA_VOCAB_TYPE_SPM = 1, // LLaMA tokenizer based on byte-level BPE with byte fallback
LLAMA_VOCAB_TYPE_UGM = 4, // T5 tokenizer based on Unigram
};
O llama.cpp carregou o vocabulário como SPM/BPE clássico, tipo 1.
Passo 3 - Checar o algoritmo real do tokenizer original, direto do binário .model:
from sentencepiece import sentencepiece_model_pb2 as model_pb2
m = model_pb2.ModelProto()
m.ParseFromString(open("tokenizer.model", "rb").read())
print(m.trainer_spec.model_type) # → 1
print(m.normalizer_spec.name) # → nmt_nfkc_cf
model_type = 1 no protobuf do SentencePiece corresponde a Unigram, não BPE. Existe também um normalizador nmt_nfkc_cf (NFKC + case-folding) configurado o tokenizer original deveria normalizar tudo pra minúsculas automaticamente antes de tokenizar.
Causa raiz
Cruzando os três passos: o tokenizer original é Unigram, com normalização de minúsculas embutida. O convert_hf_to_gguf.py gravou o GGUF como se fosse SPM/BPE clássico (tipo 1), que é o caminho de código usado por qualquer modelo com arquitetura LlamaForCausalLM.
É uma lacuna real no script de conversão. A função set_vocab() da classe usada para arquitetura Llama (conversion/llama.py) chama self._set_vocab_sentencepiece() incondicionalmente, sem checar o model_type do SentencePiece:
try:
self._set_vocab_sentencepiece() # sempre grava tokenizer.ggml.model = "llama"
except FileNotFoundError:
...
Compare com a classe usada para modelos T5 (conversion/t5.py), que já faz a checagem certa:
if sentencepiece_model.trainer_spec.model_type == 2: # BPE
return self._set_vocab_sentencepiece()
else:
assert sentencepiece_model.trainer_spec.model_type == 1 # UNIGRAM
...
self.gguf_writer.add_tokenizer_model("t5") # ativa o tipo UGM correto
...
self.gguf_writer.add_precompiled_charsmap(precompiled_charsmap)
No código-fonte do runtime (src/llama-vocab.cpp), as duas implementações mostram por que isso importa:
llm_tokenizer_spm_session::tokenize()(tipo SPM=1): reedy merge de pares de bytes por pontuação, sem nenhuma etapa de normalização.llm_tokenizer_ugm_session::tokenize()(tipo UGM=4): chamanormalize(text, &normalized)antes de tokenizar, usando oprecompiled_charsmapo mecanismo que aplicaria onmt_nfkc_cf.
Sem essa normalização, maiúsculas nunca viram minúsculas e a segmentação diverge do original. Combinado com a repeat_penalty desligada por padrão no llama.cpp, o modelo pode cair num estado degenerado e travar repetindo espaço em branco indefinidamente.
Correção
Usei a mesma lógica de checagem que a classe T5 já tem, aplicada na classe usada pela arquitetura Llama. Patch completo aqui:
--- a/conversion/llama.py
+++ b/conversion/llama.py
@@ -11,7 +11,7 @@
if TYPE_CHECKING:
from torch import Tensor
-from .base import ModelBase, TextModel, gguf, logger
+from .base import ModelBase, SentencePieceTokenTypes, TextModel, gguf, logger
@ModelBase.register(
@@ -103,6 +103,118 @@
logger.info(f"EAGLE-3: norm_before_fc = {norm_before_fc}")
self.gguf_writer.add_norm_before_fc(norm_before_fc)
+ def _sentencepiece_is_unigram(self, tokenizer_path) -> bool:
+ # Checa o model_type real gravado dentro do binário SentencePiece,
+ # em vez de assumir sempre BPE/SPM clássico (comportamento padrão do llama.cpp
+ # para arquitetura "llama", que ignora esse campo).
+ # model_type: 1 = UNIGRAM, 2 = BPE, 3 = WORD, 4 = CHAR
+ import os
+ os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
+ from sentencepiece import sentencepiece_model_pb2 as model_pb2
+
+ m = model_pb2.ModelProto() # ty: ignore[unresolved-attribute]
+ with open(tokenizer_path, "rb") as f:
+ m.ParseFromString(f.read())
+ return m.trainer_spec.model_type == 1
+
+ def _set_vocab_sentencepiece_ugm(self, tokenizer_path):
+ # Mesma lógica usada por T5Model.set_vocab() para tokenizers SentencePiece
+ # do tipo Unigram real -- inclui o precompiled_charsmap (normalizador,
+ # ex: NFKC + minúsculas), que o caminho SPM/BPE clássico não escreve
+ # e o llama.cpp não aplica para o tipo de vocabulário "llama" (SPM=1).
+ import os
+ os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
+ from sentencepiece import SentencePieceProcessor
+ from sentencepiece import sentencepiece_model_pb2 as model_pb2
+
+ sentencepiece_model = model_pb2.ModelProto() # ty: ignore[unresolved-attribute]
+ sentencepiece_model.ParseFromString(open(tokenizer_path, "rb").read())
+
+ add_prefix = sentencepiece_model.normalizer_spec.add_dummy_prefix
+ remove_whitespaces = sentencepiece_model.normalizer_spec.remove_extra_whitespaces
+ precompiled_charsmap = sentencepiece_model.normalizer_spec.precompiled_charsmap
+
+ tokenizer = SentencePieceProcessor()
+ tokenizer.LoadFromFile(str(tokenizer_path))
+
+ vocab_size = self.hparams.get('vocab_size', tokenizer.vocab_size())
+
+ tokens: list[bytes] = [f"[PAD{i}]".encode("utf-8") for i in range(vocab_size)]
+ scores: list[float] = [-10000.0] * vocab_size
+ toktypes: list[int] = [SentencePieceTokenTypes.UNUSED] * vocab_size
+
+ for token_id in range(tokenizer.vocab_size()):
+ piece = tokenizer.IdToPiece(token_id)
+ text = piece.encode("utf-8")
+ score = tokenizer.GetScore(token_id)
+
+ toktype = SentencePieceTokenTypes.NORMAL
+ if tokenizer.IsUnknown(token_id):
+ toktype = SentencePieceTokenTypes.UNKNOWN
+ elif tokenizer.IsControl(token_id):
+ toktype = SentencePieceTokenTypes.CONTROL
+ elif tokenizer.IsUnused(token_id):
+ toktype = SentencePieceTokenTypes.UNUSED
+ elif tokenizer.IsByte(token_id):
+ toktype = SentencePieceTokenTypes.BYTE
+
+ tokens[token_id] = text
+ scores[token_id] = score
+ toktypes[token_id] = toktype
+
+ added_tokens_file = self.dir_model / 'added_tokens.json'
+ if added_tokens_file.is_file():
+ with open(added_tokens_file, "r", encoding="utf-8") as f:
+ added_tokens_json = json.load(f)
+ for key in added_tokens_json:
+ token_id = added_tokens_json[key]
+ if token_id >= vocab_size:
+ logger.warning(f'ignore token {token_id}: id is out of range, max={vocab_size - 1}')
+ continue
+ tokens[token_id] = key.encode("utf-8")
+ scores[token_id] = -1000.0
+ toktypes[token_id] = SentencePieceTokenTypes.USER_DEFINED
+
+ tokenizer_config_file = self.dir_model / 'tokenizer_config.json'
+ if tokenizer_config_file.is_file():
+ with open(tokenizer_config_file, "r", encoding="utf-8") as f:
+ tokenizer_config_json = json.load(f)
+ added_tokens_decoder = tokenizer_config_json.get("added_tokens_decoder", {})
+ for token_id, token_data in added_tokens_decoder.items():
+ token_id = int(token_id)
+ token: str = token_data["content"]
+ if token_id >= vocab_size:
+ logger.warning(f'ignore token {token_id}: id is out of range, max={vocab_size - 1}')
+ continue
+ if token_data.get("special") or self.does_token_look_special(token):
+ toktypes[token_id] = SentencePieceTokenTypes.CONTROL
+ else:
+ token = token.replace(b"\xe2\x96\x81".decode("utf-8"), " ")
+ toktypes[token_id] = SentencePieceTokenTypes.USER_DEFINED
+ scores[token_id] = -1000.0
+ tokens[token_id] = token.encode("utf-8")
+
+ if vocab_size > len(tokens):
+ pad_count = vocab_size - len(tokens)
+ logger.debug(f"Padding vocab with {pad_count} token(s) - [PAD1] through [PAD{pad_count}]")
+ for i in range(1, pad_count + 1):
+ tokens.append(bytes(f"[PAD{i}]", encoding="utf-8"))
+ scores.append(-1000.0)
+ toktypes.append(SentencePieceTokenTypes.UNUSED)
+
+ self.gguf_writer.add_tokenizer_model("t5")
+ self.gguf_writer.add_tokenizer_pre("default")
+ self.gguf_writer.add_token_list(tokens)
+ self.gguf_writer.add_token_scores(scores)
+ self.gguf_writer.add_token_types(toktypes)
+ self.gguf_writer.add_add_space_prefix(add_prefix)
+ self.gguf_writer.add_remove_extra_whitespaces(remove_whitespaces)
+ if precompiled_charsmap:
+ self.gguf_writer.add_precompiled_charsmap(precompiled_charsmap)
+
+ special_vocab = gguf.SpecialVocab(self.dir_model, n_vocab=len(tokens))
+ special_vocab.add_to_gguf(self.gguf_writer)
+
def set_vocab(self):
# eagle3: use tokenizer from target model if provided
original_dir_model = None
@@ -132,13 +244,17 @@
if tokenizer_config_json.get("tokenizer_class") == "HybridDNATokenizer":
return self._set_vocab_hybriddna()
- try:
- self._set_vocab_sentencepiece()
- except FileNotFoundError:
- try:
- self._set_vocab_llama_hf()
- except (FileNotFoundError, TypeError):
- # Llama 3
- self._set_vocab_gpt2()
+ tokenizer_model_path = self.dir_model / 'tokenizer.model'
+ if tokenizer_model_path.is_file() and self._sentencepiece_is_unigram(tokenizer_model_path):
+ self._set_vocab_sentencepiece_ugm(tokenizer_model_path)
+ else:
+ try:
+ self._set_vocab_sentencepiece()
+ except FileNotFoundError:
+ try:
+ self._set_vocab_llama_hf()
+ except (FileNotFoundError, TypeError):
+ # Llama 3
+ self._set_vocab_gpt2()
Depois de aplicar e reconverter:
tokenizer.ggml.model str = t5 (antes: llama)
init_tokenizer: initializing tokenizer for type 4 (antes: type 1)
tokenizer.ggml.precompiled_charsmap arr[u8,244410] (antes: ausente)
A palavra "instrução" passa a tokenizar como um único token, idêntico ao Python. Os travamentos com maiúscula + ? somem. Testei em llama-cli e via Open WebUI, com resultado consistente nos dois.
Como aplicar
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
git apply manaca-ugm-tokenizer-fix.patch
pip install -r requirements/requirements-convert_hf_to_gguf.txt
python3 convert_hf_to_gguf.py /caminho/para/manaca-1b-instruct --outfile manaca-1b-instruct.f16.gguf --outtype f16
Importante: precisa reconverter do zero.
Esse é um bug genérico do llama.cpp afeta qualquer modelo com arquitetura Llama e tokenizer SentencePiece Unigram, não é peculiaridade deste modelo. Por enquanto não abri PR upstream, já que é um caso relativamente raro, mas deixo o patch documentado aqui.
Bônus: chat_template ausente
Vale mencionar um segundo problema, sem relação com o bug acima, que também afeta o uso via Open WebUI/chat.
O tokenizer_config.json deste modelo não tem o campo chat_template. Sem ele, ferramentas como llama-cli -chat ou o /v1/chat/completions do Open WebUI caem num fallback genérico (ChatML), que não bate com o formato real usado no treino Alpaca-PT (### Instrução: / ### Resposta:, conforme documentado no README).
O sintoma é bem diferente do bug de tokenização: respostas vazias ou a pergunta ecoada de volta, especificamente em modo chat, enquanto completion crua com o prompt montado manualmente funciona bem.
Solução local: passar um chat_template customizado em Jinja via --chat-template-file:
Abaixo está uma instrução que descreve uma tarefa. Escreva uma resposta que atenda adequadamente ao pedido.
{% for message in messages %}{% if message['role'] == 'user' %}### Instrução:
{{ message['content'] }}
{% elif message['role'] == 'assistant' %}### Resposta:
{{ message['content'] }}
{% endif %}{% endfor %}{% if add_generation_prompt %}### Resposta:
{% endif %}
./llama-server -m manaca-1b-instruct.f16.gguf --chat-template-file alpaca.jinja
Ressalva: o Alpaca original foi treinado só pra um turno (uma instrução, uma resposta). Esse template é uma aproximação pra conversas com múltiplos turnos o modelo nunca viu esse padrão no treino, então a qualidade tende a cair conforme a conversa cresce.
