Una implementación de codificación para ejecutar modelos de razonamiento Qwen3.5 destilados con pensamiento estilo Claude utilizando GGUF y cuantificación de 4 bits

En este tutorial, trabajamos directamente con modelos Qwen3.5 destilados con un razonamiento estilo Claude y configuramos una canalización de Colab que nos permite cambiar entre una variante 27B GGUF y una versión liviana 2B de 4 bits con una sola bandera. Comenzamos validando la disponibilidad de la GPU, luego instalamos condicionalmente llama.cpp o transformadores con bitsandbytes, según la ruta seleccionada. Ambas ramas están unificadas a través de interfaces compartidas generate_fn y stream_fn, lo que garantiza una inferencia coherente entre los backends. También implementamos una clase ChatSession para interacción de múltiples turnos y creamos utilidades para analizar seguimientos, lo que nos permite separar explícitamente el razonamiento de los resultados finales durante la ejecución.

MODEL_PATH = "2B_HF" importar antorcha si no es torch.cuda.is_available(): elevar RuntimeError ("❌ ¡No hay GPU! Vaya a Runtime → Cambiar tipo de tiempo de ejecución → T4 GPU." ) gpu_name = torch.cuda.get_device_name(0) vram_gb = torch.cuda.get_device_properties(0).total_memory / 1e9 print(f"✅ GPU: {gpu_name} — {vram_gb:.1f} GB VRAM") subproceso de importación, sys, os, re, time generate_fn = Ninguno stream_fn = Ninguno

Inicializamos la ejecución configurando el indicador de ruta del modelo y verificando si hay una GPU disponible en el sistema. Recuperamos e imprimimos el nombre de la GPU junto con la VRAM disponible para garantizar que el entorno cumpla con los requisitos. También importamos todas las bibliotecas base necesarias y definimos marcadores de posición para las funciones de generación unificadas que se asignarán más adelante.

if MODEL_PATH == "27B_GGUF": print("n📦 Instalar llama-cpp-python con CUDA (tarda de 3 a 5 minutos)…") env = os.environ.copy() env["CMAKE_ARGS"] = "-DGGML_CUDA=on" subprocess.check_call( [sys.executable, "-m", "pip", "install", "-q", "llama-cpp-python", "huggingface_hub"], env=env, ) print("✅ Instalado.n") from huggingface_hub import hf_hub_download from llama_cpp import Llama GGUF_REPO = "Jackrong/Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-GGUF" GGUF_FILE = "Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled-Q4_K_M.gguf" print(f"⏳ Descargando {GGUF_FILE} (~16.5 GB)… toma un café ☕") model_path = hf_hub_download(repo_id=GGUF_REPO, filename=GGUF_FILE) print(f"✅ Descargado: {model_path}n") print("⏳ Cargando en llama.cpp (descarga de GPU)…") llm = Llama( model_path=model_path, n_ctx=8192, n_gpu_layers=40, n_threads=4, verbose=False, ) print("✅ Modelo 27B GGUF cargado!n") def generate_fn( Prompt, system_prompt="Usted es un asistente útil Piense paso a paso.", max_new_tokens=2048, temperatura=0.6, top_p=0.95, **kwargs): salida = llm.create_chat_completion( mensajes=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": Prompt}, ], max_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, ) devuelve salida["opciones"][0]["message"]["content"] def stream_fn( Prompt, system_prompt="Eres un asistente útil. Piensa paso a paso.", max_new_tokens=2048, Temperature=0.6, top_p=0.95, ): print("⏳ Salida de streaming:n") para fragmento en llm.create_chat_completion( mensajes=[ {"role": "system", "content": system_prompt}, {"rol": "usuario", "contenido": aviso}, ], max_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, flujo=Verdadero,): delta = fragmento["opciones"][0].get("delta", {}) text = delta.get("content", "") if text: print(text, end="", Flush=True) print() class ChatSession: def __init__(self, system_prompt="Eres un asistente útil. Piensa paso a paso."): self.messages = [{"role": "system", "content": system_prompt}] def chat(self, user_message, Temperature=0.6): self.messages.append({"role": "usuario", "content": user_message}) salida = llm.create_chat_completion( mensajes=self.messages, max_tokens=2048, temperatura=temperatura, top_p=0.95, ) resp = salida["opciones"][0]["mensaje"]["contenido"] self.messages.append({"role": "asistente", "contenido": resp}) return resp

Manejamos la ruta 27B GGUF instalando llama.cpp con soporte CUDA y descargando el modelo destilado Qwen3.5 27B de Hugging Face. Cargamos el modelo con descarga de GPU y definimos un generate_fn y un stream_fn estandarizados para inferencia y salidas de transmisión. También implementamos una clase ChatSession para mantener el historial de conversaciones para interacciones de varios turnos.

elif MODEL_PATH == "2B_HF": print("n📦 Instalando transformadores + bitsandbytes…") subprocess.check_call([ sys.executable, "-m", "pip", "install", "-q", "transformers @ git+https://github.com/huggingface/transformers.git@main", "accelerate", "bitsandbytes", "sentencepiece", "protobuf", ]) print("✅ Instalado.n") desde transformadores import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TextStreamer HF_MODEL_ID = "Jackrong/Qwen3.5-2B-Claude-4.6-Opus-Reasoning-Distilled" bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) print(f"⏳ Cargando {HF_MODEL_ID} en 4 bits…") tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_ID, trust_remote_code=True) modelo = AutoModelForCausalLM.from_pretrained( HF_MODEL_ID, quantization_config=bnb_config, device_map="auto", trust_remote_code=True, torch_dtype=torch.bfloat16, ) print(f"✅ ¡Modelo cargado! Memoria: {model.get_memory_footprint() / 1e9:.2f} GBn") def generate_fn( solicitud, system_prompt="Eres un asistente útil. Piensa paso a paso.", max_new_tokens=2048, Temperature=0.6, top_p=0.95, repetition_penalty=1.05, do_sample=True, **kwargs): mensajes = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": Prompt}, ] text = tokenizer.apply_chat_template(messages, tokenize=False, add_spawn_prompt=True) inputs = tokenizer(text, return_tensors="pt").to(model.device) con torch.no_grad(): output_ids = model.generate( **inputs, max_new_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, repetition_penalty=repetition_penalty, do_sample=do_sample, ) generado = output_ids[0][entradas["input_ids"].forma[1]:] return tokenizer.decode(generated, skip_special_tokens=True) def stream_fn( Prompt, system_prompt="Eres un asistente útil. Piensa paso a paso.", max_new_tokens=2048, Temperature=0.6, top_p=0.95, ): mensajes = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": Prompt}, ] texto = tokenizer.apply_chat_template(messages, tokenize=False, add_spawn_prompt=True) inputs = tokenizer(text, return_tensors="pt").to(model.device) streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) print("⏳ Salida de streaming:n") con torch.no_grad(): model.generate( **inputs, max_new_tokens=max_new_tokens, temperatura=temperatura, top_p=top_p, do_sample=True, streamer=streamer, ) class ChatSession: def __init__(self, system_prompt="Eres un asistente útil. Piensa paso a paso."): self.messages = [{"role": "system", "content": system_prompt}] def chat(self, mensaje_usuario, temperatura=0.6): self.messages.append({"role": "usuario", "content": mensaje_usuario}) texto = tokenizer.apply_chat_template(self.messages, tokenize=False, add_generación_prompt=True) entradas = tokenizer(texto, return_tensors="pt").to(model.device) con torch.no_grad(): output_ids = model.generate( **entradas, max_new_tokens=2048, temperatura=temperatura, top_p=0.95, do_sample=True, ) generado = output_ids[0][entradas["input_ids"].forma[1]:] resp = tokenizer.decode(generated, skip_special_tokens=True) self.messages.append({"role": "assistant", "content": resp}) return resp else: rise ValueError("MODEL_PATH debe ser '27B_GGUF' o '2B_HF'")

Implementamos la ruta ligera 2B utilizando transformadores con cuantificación de 4 bits mediante bitsandbytes. Cargamos el modelo destilado Qwen3.5 2B de manera eficiente en la GPU y configuramos los parámetros de generación para un muestreo controlado. Nuevamente definimos la lógica unificada de generación, transmisión y sesión de chat para que ambas rutas del modelo se comporten de manera idéntica durante la ejecución.

def parse_thinking(respuesta: str) -> tupla: m = re.search(r"(.*?)", respuesta, re.DOTALL) if m: return m.group(1).strip(), respuesta[m.end():].strip() return "", respuesta.strip() def display_response(respuesta: str): pensando, respuesta = parse_thinking(respuesta) if pensando: print("🧠 PENSAR:") print("-" * 60) print(thinking[:1500] + ("n… [truncado]" if len(thinking) > 1500 else "")) print("-" * 60) print("n💬 RESPUESTA:") print(respuesta) print("✅ Todos los ayudantes listos. Ejecutando pruebas…n")

Definimos funciones auxiliares para extraer rastros de razonamiento encerrados dentro de etiquetas y separarlas de las respuestas finales. Creamos una utilidad de visualización que formatea e imprime tanto el proceso de pensamiento como la respuesta de forma estructurada. Esto nos permite inspeccionar cómo el modelo basado en Qwen razona internamente durante la generación.

print("=" * 70) print("📝 PRUEBA 1: Razonamiento básico") print("=" * 70) respuesta = generate_fn( "Si tengo 3 manzanas y regalo la mitad, luego compro 5 más, ¿cuántas tengo? " "Explica tu razonamiento." ) display_response(respuesta) print("n" + "=" * 70) print("📝 PRUEBA 2: Salida en streaming") print("=" * 70) stream_fn( "Explica la diferencia entre concurrencia y paralelismo. " "Da una analogía del mundo real para cada uno." ) print("n" + "=" * 70) print("📝 PRUEBA 3: Pensar ON versus OFF") print("=" * 70) question = "¿Cuál es la capital de Francia?" print("n— Pensando ON (predeterminado) —") resp = generate_fn(question) display_response(resp) print("n— Pensando OFF (conciso) —") resp = generate_fn( question, system_prompt="Responda directa y concisa. No use etiquetas.", max_new_tokens=256, ) display_response(resp) print("n" + "=" * 70) print("📝 PRUEBA 4: Pregunta con truco de bate y pelota") print("=" * 70) respuesta = generate_fn( "Un bate y una pelota cuestan $1.10 en total. " "¿Cuánto cuesta la pelota? Muestra el razonamiento completo y verifica.", system_prompt="Eres un razonador matemático preciso. Establece ecuaciones y verifica.", temperatura=0.3,) display_response(respuesta) print("n" + "=" * 70) print("📝 PRUEBA 5: Problema de encuentro de trenes") print("=" * 70) respuesta = generate_fn( "Un tren sale de la estación A a las 9:00 a. m. a 60 mph hacia la estación B. " "Otro sale de la estación B a las 10:00 a. m. a 80 mph hacia la estación A. " "Las estaciones están a 280 millas de distancia. ¿Cuándo y ¿dónde se encuentran?", temperatura=0.3, ) display_response(respuesta) print("n" + "=" * 70) print("📝 TEST 6: Rompecabezas de lógica (cinco casas)") print("=" * 70) respuesta = generate_fn( "Cinco casas seguidas están pintadas de diferentes colores. " "La casa roja está a la izquierda de la casa azul. " "La casa verde está en el medio. " "La casa amarilla no está al lado de la azul house. " "La casa blanca está en un extremo. " "¿Cuál es el orden de izquierda a derecha?", temperatura=0.3, max_new_tokens=3000, ) display_response(response) print("n" + "=" * 70) print("📝 PRUEBA 7: Generación de código: subcadena palindrómica más larga") print("=" * 70) respuesta = generate_fn( "Escribe una función de Python para encontrar la más larga subcadena palindrómica " "usando el algoritmo de Manacher. Incluye cadena de documentación, sugerencias de tipo y pruebas.", system_prompt="Eres un programador experto en Python. Piensa detenidamente en el algoritmo.", max_new_tokens=3000, temperatura=0.3, ) display_response(response) print("n" + "=" * 70) print("📝 PRUEBA 8: Conversación de varios turnos (tutor de física)") print("=" * 70) session = ChatSession( system_prompt="Eres un tutor de física experto. Explícalo claramente con ejemplos." ) turns = [ "¿Qué es el principio de incertidumbre de Heisenberg?", "¿Puedes darme un ejemplo concreto con números reales?", "¿Cómo se relaciona esto con el túnel cuántico?", ] para i, q en enumerate(turns, 1): print(f"n{'─'*60}") print(f"👤 Turn {i}: {q}") print(f"{'─'*60}") resp = session.chat(q, temperatura=0.5) _, respuesta = parse_thinking(resp) print(f"🤖 {answer[:1000]}{'…' if len(respuesta) > 1000 else ''}") print("n" + "=" * 70) print("📝 TEST 9: Temperatura comparación – escritura creativa") print("=" * 70) creative_prompt = "Escribe una introducción de un párrafo para una historia de ciencia ficción sobre la conciencia de la IA." configs = [ {"label": "Temperatura baja (0.1)", "temperatura": 0.1, "top_p": 0.9}, {"label": "Temperatura media (0.6)", "temperatura": 0.6, "top_p": 0.95}, {"label": "Temperatura alta (1.0)", "temperatura": 1.0, "top_p": 0.98}, ] para cfg en configuraciones: print(f"n🎛️ {cfg['label']}") print("-" * 60) start = time.time() resp = generate_fn( creative_prompt, system_prompt="Eres un escritor de ficción creativo.", max_new_tokens=512, Temperature=cfg["temperature"], top_p=cfg["top_p"], ) transcurrido = time.time() – start _, respuesta = parse_thinking(resp) print(answer[:600]) print(f"⏱️ {elapsed:.1f}s") print("n" + "=" * 70) print("📝 PRUEBA 10: Prueba comparativa de velocidad") print("=" * 70) start = time.time() resp = generate_fn( "Explica cómo aprende una red neuronal, paso a paso, para un principiante.", system_prompt="Eres un paciente, claro maestro.", max_new_tokens=1024, ) transcurrido = time.time() – start approx_tokens = int(len(resp.split()) * 1.3) print(f"~{approx_tokens} tokens in {elapsed:.1f}s") print(f"~{approx_tokens / transcurrido:.1f} tokens/seg") print(f"GPU: {torch.cuda.get_device_name(0)}") print(f"VRAM pico: {torch.cuda.max_memory_allocated() / 1e9:.2f} GB") import gc para el nombre en ["model", "llm"]: si el nombre está en globals(): del globals()[nombre] gc.collect() torch.cuda.empty_cache() print(f"n✅ Memoria liberada. VRAM: {torch.cuda.memory_allocated() / 1e9:.2f} GB") print("n" + "=" * 70) print("🎉 ¡Tutorial completo!") print("=" * 70)

Ejecutamos un conjunto de pruebas integral que evalúa el modelo mediante razonamiento, transmisión, acertijos lógicos, generación de código y conversaciones de varios turnos. Comparamos salidas bajo diferentes configuraciones de temperatura y medimos el rendimiento en términos de velocidad y rendimiento del token. Finalmente, limpiamos la memoria y liberamos recursos de la GPU, asegurando que la computadora portátil siga siendo reutilizable para futuros experimentos.

En conclusión, tenemos una configuración compacta pero flexible para ejecutar modelos de razonamiento basados ​​en Qwen3.5 mejorados con destilación estilo Claude a través de diferentes restricciones de hardware. El script abstrae las diferencias de backend al tiempo que expone interfaces de generación, transmisión y conversación consistentes, lo que facilita experimentar con el comportamiento de razonamiento. A través del conjunto de pruebas, probamos cómo el modelo maneja el razonamiento estructurado, las preguntas de casos extremos y las tareas más largas de varios pasos, al mismo tiempo que medimos la velocidad y el uso de la memoria. Lo que obtenemos no es solo una demostración, sino una plataforma reutilizable para evaluar y ampliar los sistemas de razonamiento basados ​​en Qwen en Colab sin cambiar el código central.

Consulte el cuaderno completo y la página fuente. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 120.000 ML y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.