Llama3.2 3b Español

Este articulo está basado en una transcripción de una serie de videos de live-coding con el modelo whisper y ChatGPT o1-preview. Playlist Codigo Fuente Completo del Articulo
Introducción
Hasta ahora, todos los modelos de lenguaje pequeño que he intentado utilizar tienen el problema de no ser muy efectivos en español. Si bien pueden ser útiles para herramientas o para realizar cadenas de pensamiento y diferentes formas de prompts, si no responden adecuadamente en español, no tiene sentido usarlos localmente.
La última versión antes de esta fue Llama 3.1, que salió hace aproximadamente dos meses. Esta versión 3.1 solo tenía modelos medianos y grandes, siendo el modelo más pequeño de aproximadamente 8 o 9 billones de parámetros. Este modelo apenas podía correr en mi tarjeta de video, una GTX 1080 Ti, que es algo viejita, pero tiene buena capacidad de procesamiento. Al utilizar la versión que tenía, requería alrededor de 8 gigabytes de memoria de video, pero no era óptima para las funciones que utilizamos para generar extracciones.
Llama 3.2
Este nuevo modelo agrega capacidades de visión a los modelos existentes, lo cual es interesante, aunque actualmente no estamos trabajando con visión. Además, los runtimes que utilizo para correr y probar localmente no tienen soporte para visión, o al menos tardan más en implementarlo. Las versiones que salieron solo para texto tienen modelos de 1 y 3 billones de parámetros.
Microsoft lanzó un modelo llamado Phi 3 Small, que me pareció muy bueno, pero todas mis pruebas fueron en inglés. En español, el modelo a veces cambiaba a inglés en medio de la respuesta, lo que no tenía sentido. El otro modelo que se menciona es el Gemma 2 , de 2 billones de parámetros, que ha sido calificado como superior en varias ocasiones, aunque no es tan efectivo en español. Se puede usar el Google AI Studio para probar Gemma 2B, que es gratuito y accesible programáticamente a través de sus endpoints.
He probado varios modelos, y el de 3B, en particular, me ha funcionado bien para hacer llamadas a herramientas. Sin embargo, el modelo en LM Studio no responde adecuadamente cuando le pido que use herramientas; me devuelve JSON, pero incorrecto. Por otro lado, en OLlama ha funcionado bien, y aún tengo que hacer pruebas con el modelo de un billón de parámetros.
Este modelo tiene soporte para un contexto de 128,000 tokens, lo que permite enviar mucha información al solicitar, por ejemplo, hacer una extracción. Hasta ahora, hemos estado manejando modelos de 128,000 tokens, como GPT-4o-mini, mientras que el único que supera esto es el de Google, el Gemini 1.5 Flash. Sin embargo, este modelo falla frecuentemente y a veces se queda generando tokens indefinidamente.
En cuanto al rendimiento de modelos de 3b de parámetros con 128,000 tokens, se requiere una tarjeta de video con al menos 10.5 a 12 gigabytes de memoria para su funcionamiento óptimo. Mis pruebas se realizaron con prompts básicos; por ejemplo, le envié texto en español y le pedí un resumen de tres historias. Para correr esto, necesito tener el modelo de Llama 3.2 en OLlama.
Capacidad de Resumir
Generé las historias con OpenAI, donde pedí que fueran sobre tres personajes, con un título y una muerte involucrada. Posteriormente, le pedí al modelo que me generara un resumen, especificando que debía responder solo en español. Esto me devolvió un resumen apropiado.
string textStory01 = await File.ReadAllTextAsync("Historia01.txt");
string textStory02 = await File.ReadAllTextAsync("Historia02.txt");
string textStory03 = await File.ReadAllTextAsync("Historia03.txt");
var response = await aiService.CompleteAsync($"Generame un resumen de 1 parrafo de la siguiente historia:\n{textStory01}", "Eres un bot que resume historias, responde solo en español");
Resultado:
Tres amigos de infancia se reunieron para una última partida de ajedrez en un parque tranquilo del pueblo de San Andrés. La conversación se volvió nostálgica mientras juegaban, pero la alegría se interrumpió cuando un accidente ocurrió en la carretera cercana y el más aventurero, Hugo, sacudió a su vida para salvar a alguien atrapado, aunque murió en el acto. La tragedia los dejó con lágrimas en los ojos, pero también les enseñó que su recuerdo viviría siempre en sus corazones y en cada partida futura, transformando la muerte en un homenaje a su espíritu.
Zero-shot y Few-shot Prompting
También experimenté con zero-shot prompting, donde le pedí al modelo que extrajera nombres de una historia sin darle instrucciones específicas. Me devolvió una lista que incluía nombres, aunque en un caso no se mencionó el nombre del abuelo. Luego, probé con few-shot prompting, donde proporcioné ejemplos previos y le pedí que generara una lista de nombres con un formato especifico de la misma historia. Esto funcionó bien, y el modelo entendió el formato requerido.
Zero Shot:
ChatMessage[] singleShotMessages =
[
new SystemChatMessage("Eres un bot extractor de nombres de historias"),
new UserChatMessage($"Con la siguiente historia\n{textStory03}, Dame una lista de todos los nombres")
];
var singleShotNames = await aiService.CompleteAsync(singleShotMessages);
Respuesta:
Aquí te presento la lista de nombres mencionados en la historia:
1. Valentina (32 años)
2. Tomás (30 años)
3. Ana (31 años)
4. El abuelo de Valentina (no se especifica su edad)
Además, hay un personaje que no tiene nombre destacado, se refiere a la familia de Valentina que es dueña del viñedo, pero no se menciona su nombre.
Espero que esta lista sea útil. ¡Si necesitas algo más, no dudes en preguntar!
Few Shot:
//few shot example
ChatMessage[] fewShotMessages =
[
new SystemChatMessage("Eres un bot extractor de nombres de historias"),
new UserChatMessage($"Con la siguiente historia:\n{textStory01}, Dame una lista de todos los nombres"),
new AssistantChatMessage("La Última Partida:\n\tMartín (28 años)\n\tClara (26 años)\n\tHugo (27 años)\n"),
new UserChatMessage($"Con la siguiente historia:\n{textStory02}, Dame una lista de todos los nombres"),
new AssistantChatMessage("El Último Viaje:\n\tPedro (30 años)\n\tSofía (29 años)\n\tMiguel (31 años)\n"),
new UserChatMessage($"Con la siguiente historia\n{textStory03}, Dame una lista de todos los nombres")
];
var responseNames = await aiService.CompleteAsync(fewShotMessages);
Respuesta:
Recuerdos en el Viñedo:
Valentina (32 años)
Tomás (30 años)
Ana (31 años)
Razonamiento Interno
Parece que Llama 3.2 se entrenó con CoT en su conjunto de datos, porque es muy bueno explicando su razonamiento cuando se le pide en el prompt. Pero tiene problemas al intentar seguir una serie de pasos de razonamiento que se le dan en el mismo.
CoT:
var cotResponse = await aiService.CompleteAsync("¿\"somos\" es un palindromo? explicame tu razonamiento", "Eres un bot con razonamiento avanzado, eres bueno para describir lo que piensas y solo respondes en español");
var cotComplexResponse = await aiService.CompleteAsync(
"""
"somos" es un palindromo? explicame tu razonamiento.
describe los siguientes pasos para saber si "somos" es un palindromo:
Paso 1.- ¿Cuales son las reglas de un palindromo?.
Paso 2.- Aplica las reglas de un palindromo a la palabra "somos".
Paso 3.- Provee la respuesta.
""",
"Eres un bot con razonamiento avanzado, eres bueno para describir lo que piensas y solo respondes en español");
Respuesta CoT Simple:
CoT Simple Example:
Para determinar si "somos" es un palíndromo, primero debemos entender qué es un palíndromo. Un palíndromo es una palabra, frase o secuencia de caracteres que se lee igual al revés. En otras palabras, si escribimos la palabra "somos", mirando hacia atrás y adelante, deben ser exactamente iguales.
En este caso, si escribimos "somos" y miramos hacia atrás y adelante, obtenemos la misma secuencia de caracteres: S-O-M-O-S. Como las letras se alinean perfectamente en ambos sentidos, "somos" cumple con el definitivo requisito para ser considerado un palíndromo.
Por lo tanto, concluyo que sí, "somos" es un palíndromo.
Respuesta CoT Compleja:
¡Claro! Me emociona explicar esto.
Respuesta: sí, "somos" es un palindromo.
Aquí te describo los pasos para comprobar si una palabra es un palindromo:
**Paso 1.- ¿Cuales son las reglas de un palindromo?**
Una palabra es un palindromo si la forma en que se escribe está idéntica al revés, es decir, si la primera letra es igual a la última letra, el segundo término del primero término, el tercer término del último término, y así sucesivamente.
**Paso 2.- Aplica las reglas de un palindromo a la palabra "somos"**
Para comprobar si "somos" es un palindromo, simplemente debemos mirar al revés la palabra:
Original: somos
Reversa: sosom
Como es evidente que no hay coincidencia entre el original y su inversión, podemos concluir que...
**Paso 3.- Provee la respuesta.**
Hasta ahora hemos concluido que "somos" no satisface las reglas para ser considerado un palindromo, ya que al mirar al revés la palabra, vemos que no estamos ante una inversión perfecta de la misma manera que lo hace "madam" o "radar".
Prueba Real: Extracción de Documentos
Voy a crear una herramienta para extraer catálogos de conceptos de documentos de la Secretaría de Obras Públicas del estado de Sinaloa.
Para comenzar, voy a realizar una extracción rápida de todos los documentos. Estoy utilizando la librería directamente de OpenAI para este proyecto. Lo único que necesito hacer es cambiar la conexión al servidor de inferencia al que me voy a conectar. Para usar modelos locales con Ollama:
var options = new OpenAIClientOptions()
{
Endpoint = new Uri("localhost:1134") //
};
ChatClient chatClient = new("llama3.2", "ollama");
También lo probé con LM Studio. En LM Studio, lo que cambia es el nombre del modelo.
var options = new OpenAIClientOptions()
{
Endpoint = new Uri("localhost:1134") //
};
//lm studio
ChatClient chatClient = new("lmstudio-community/Llama-3.2-3B-Instruct-GGUF", "lm-studio");
Finalmente, si tenemos el objetivo de extraer una lista de conceptos, generamos un esquema JSON para un tool call de OpenAI que nos proporcione esos datos:
var tool = aiService.GetTool("ExtractorConceptos", "Extrae Catalogo de Conceptos",
"""
{
"type": "object",
"properties": {
"conceptos": {
"type": "array",
"description": "Una lista de conceptos del documento de catalogo de conceptos",
"items": {
"type": "object",
"properties": {
"clave": {
"type": "string",
"description": "clave del concepto"
},
"descripcion": {
"type": "string",
"description": "descripcion del concepto"
}
},
"required": ["clave", "description"]
}
}
},
"required": ["conceptos"]
}
""");
Codigo
Para la lectura de PDFs voy a usar la librería de PdfiumCore. Estoy pensando en escribir un artículo sobre cómo usar esta librería en distintos lenguajes y contar por qué es la libreria más eficiente que he encontrado para trabajar con PDFs. Se puede extraer texto de PDFs a una tasa sorprendente.
El código para generar un prompt por cada página sería:
//debemos sacar codigo unsafe de codigo asincrono
static string GetString(FpdfTextpageT pageTextT)
{
unsafe
{
//agarramos el numero de caracteres
int characterCount = FPDFTextCountChars(pageTextT);
//cada caracteres 2 bytes + null al final (c string)
Span<byte> txt = new byte[characterCount * 2 + 1];
fixed (byte* txtPtr = txt)
{
//public/fpdf_text.h
//pasamos puntero a CLR fixed byte[]
FPDFTextGetText(pageTextT, 0, characterCount, ref *(ushort*)txtPtr);
}
//convertimos a unicode
return Encoding.Unicode.GetString(txt);
}
}
FPDF_InitLibrary(); //inicializa Pdfium
foreach (var filename in Directory.EnumerateFiles(folder, "*.pdf", SearchOption.AllDirectories))
{
if (!filename.Contains("CONCEPTOS", StringComparison.OrdinalIgnoreCase)) continue;
var documenT = FPDF_LoadDocument(filename, null); //abrir documento
int pageCount = FPDF_GetPageCount(documenT);
for (int i = 0; i < pageCount; i++)
{
var pageT = FPDF_LoadPage(documenT, i); //abrir pagina
var pageTextT = FPDFTextLoadPage(pageT); //abrir texto
string pageText = GetString(pageTextT);
if (pageText.Length > 200)
{
//prompt para la extraccion
ChatMessage[] chatMessages = [
new SystemChatMessage("Eres un bot de extraccion de datos de catalogos de conceptos, responde solo con json valido."),
new UserChatMessage($@"Con los siguientes datos de un catalogo de conceptos \n```\n{pageText}\n```\n
Extrae una lista de conceptos con un arreglo de clave y descripcion con el formato de json: {{conceptos: [{{clave: ""Clave del concepto"", descripcion: ""Descripcion del concepto""}}]}}.")
];
var options = new ChatCompletionOptions() { Tools = { tool } };
var response = await aiService.CompleteAsync(chatMessages, options);
try
{
var parsed = JsonSerializer.Deserialize<Concepto>(response);
Console.WriteLine($"Parsed Correctly");
}
catch
{
Console.WriteLine($"Failed to parse Json");
}
}
FPDFTextClosePage(pageTextT); //liberar pagina
FPDF_ClosePage(pageT); //liberar texto
}
FPDF_CloseDocument(documenT); //liberar documento
}
FPDF_DestroyLibrary();
Resultados
En mis pruebas, he encontrado que algunos modelos simplemente no funcionan como se esperaba. Por ejemplo, al utilizar este modelo, la respuesta fue completamente inadecuada.
Llama 3.2 me respondía con estructuras de tool, pero no respetaban la estructura que esperaba. Quería ver si podíamos utilizar esto para que, por ejemplo, un usuario definiera una extracción con los datos que él espera, y nosotros simplemente llamarlo y esperar que nos regresara los datos correctos. En este caso, ya nos dimos cuenta de que, por ejemplo, OpenAI no se ha equivocado ninguna vez, a pesar de que es un modelo pequeño y muy económico.
En otros casos cuando el modelo agarra el tool, no me responde con los conceptos que realmente necesito. En su lugar, recibo un esquema completo que incluye parámetros, conceptos y descripciones, pero pueden ser cadenas vacias o nulas.
Al final, lo que podría sugerir en última instancia es que le indique que extraiga los conceptos utilizando el formato JSON. Le he estado dando ejemplos, y es probable que eso lo ayude. Estoy tratando de asegurarme de que el esquema que envíe sea el correcto.
ChatMessage[] chatMessages = [
new SystemChatMessage("Eres un bot de extraccion de datos de catalogos de conceptos, responde solo con json valido."),
new UserChatMessage($@"Con los siguientes datos de un catalogo de conceptos \n```\n{pageText}\n```\n
Extrae una lista de conceptos con un arreglo de clave y descripcion con el formato de json: {{conceptos: [{{clave: ""Clave del concepto"", descripcion: ""Descripcion del concepto""}}]}}.")
];
He estado probando diferentes formas de estructurar el JSON, tratando de definir un arreglo de conceptos y varias maneras de facilitarle el trabajo al modelo, pero los resultados no son favorables.
Conclusion
Hasta ahora, el modelo más pequeño que ha funcionado en casi todos nuestros casos es el de GPT-4o-mini. Esta investigación es crucial para encontrar modelos que ofrezcan mejores resultados a precios más bajos, y en este caso, este modelo se planteaba como una buena opción para poder realizar aplicaciones con IA generativa en hardware de consumo general. Es sorprendente lo que puede hacer este modelo, pero por ahora no es un modelo adecuado para nuestras necesidades de extracción de datos.