Coverage for wrapper/cogview/wrapper_cogview.py: 45%

110 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 04:47 +0000

1""" 

2from diffusers import CogView4Pipeline 

3import torch 

4 

5pipe = CogView4Pipeline.from_pretrained("THUDM/CogView4-6B", torch_dtype=torch.bfloat16).to("cuda") 

6 

7# Open it for reduce GPU memory usage 

8pipe.enable_model_cpu_offload() 

9pipe.vae.enable_slicing() 

10pipe.vae.enable_tiling() 

11 

12prompt = "A vibrant cherry red sports car sits proudly under the gleaming sun, its polished exterior smooth " 

13prompt += "and flawless, casting a mirror-like reflection. The car features a low, aerodynamic body, angular " 

14prompt += "headlights that gaze forward like predatory eyes, and a set of black, high-gloss racing rims that " 

15prompt += "contrast starkly with the red. " 

16prompt += "A subtle hint of chrome embellishes the grille and exhaust, while the tinted windows suggest a " 

17prompt += "luxurious and private interior. The scene conveys a sense of speed and elegance, the car appearing as " 

18prompt += "if it's about to burst into a sprint along a coastal road, with the ocean's azure waves crashing in " 

19prompt += "the background." 

20image = pipe( 

21 prompt=prompt, 

22 guidance_scale=3.5, 

23 num_images_per_prompt=1, 

24 num_inference_steps=50, 

25 width=1024, 

26 height=1024, 

27).images[0] 

28 

29image.save("cogview4.png") 

30""" 

31 

32import logging 

33import os 

34import sys 

35import random 

36 

37from typing import Optional 

38from typing import Dict 

39from typing import Union 

40from typing import Any 

41 

42from PIL import Image 

43 

44import torch 

45from torch import inference_mode 

46 

47from wrapper_model import ModelGeneration 

48 

49from diffusers import CogView4Pipeline 

50 

51from xfuser.config import EngineConfig 

52 

53 

54class CogViewGeneration(ModelGeneration): 

55 """Handle image generation using the CogView model.""" 

56 MODEL_NAME = "THUDM/CogView4-6B" 

57 

58 def __init__( 

59 self, 

60 model_name: str = "cogview", 

61 engine_config: Optional[EngineConfig] = None, 

62 param_dtype: torch.dtype = torch.bfloat16, 

63 ) -> None: 

64 super().__init__(model_name) 

65 

66 self.engine_config = engine_config 

67 if self.engine_config is not None: 

68 self.torch_compile = self.engine_config.runtime_config.use_torch_compile 

69 self.param_dtype = param_dtype 

70 

71 # Parallelism 

72 self.GPU = None 

73 if torch.cuda.is_available(): 

74 self.GPU = torch.cuda.get_device_name(0) 

75 

76 self.base_seed = random.randint(0, sys.maxsize) 

77 

78 # Model components 

79 self.pipeline: Optional[CogView4Pipeline] = None 

80 

81 def __del__(self) -> None: 

82 # Clean models 

83 if self.pipeline is not None: 

84 self.pipeline = None 

85 

86 def init_parallelism(self) -> None: 

87 self.load_timer.start("torch_dist") 

88 

89 self.rank = int(os.getenv("RANK", 0)) 

90 self.local_rank = int(os.getenv("LOCAL_RANK", 0)) 

91 self.world_size = int(os.getenv("WORLD_SIZE", 1)) 

92 

93 self.device_id = self.local_rank 

94 self.device = torch.device(f"cuda:{self.device_id}") 

95 

96 torch.cuda.set_device(self.local_rank) 

97 

98 if self.world_size > 1: 

99 logging.warning("CogView is not optimized for multi-GPU setups (yet).") 

100 self.world_size = 1 

101 

102 self.load_timer.end("torch_dist") 

103 

104 def load_model(self) -> None: 

105 assert torch.cuda.is_available() 

106 

107 self.load_timer.start("pipeline") 

108 self.pipeline = CogView4Pipeline.from_pretrained( 

109 pretrained_model_name_or_path=self.MODEL_NAME, 

110 torch_dtype=self.param_dtype, 

111 ) 

112 assert self.pipeline is not None 

113 self.pipeline = self.pipeline.to(self.device) # type: ignore[union-attr] 

114 

115 # Enable memory optimizations 

116 """ 

117 self.pipeline.enable_model_cpu_offload() 

118 self.pipeline.vae.enable_slicing() 

119 self.pipeline.vae.enable_tiling() 

120 """ 

121 

122 self.load_timer.end("pipeline") 

123 

124 logging.info(f"Loaded CogView4Pipeline: {self.MODEL_NAME} device:{self.device} dtype:{self.param_dtype}.") 

125 

126 def init_model_parallelism(self) -> None: 

127 # CogView4 doesn't support model parallelism yet 

128 pass 

129 

130 def model_compile(self) -> None: 

131 if not self.torch_compile: 

132 return 

133 

134 # TODO this is not likely supported 

135 self.load_timer.start("compile") 

136 torch._inductor.config.reorder_for_compute_comm_overlap = True 

137 assert self.pipeline is not None 

138 if hasattr(self.pipeline, 'transformer'): 

139 self.pipeline.transformer = torch.compile( 

140 self.pipeline.transformer, 

141 mode="max-autotune-no-cudagraphs" 

142 ) 

143 self.load_timer.end("compile") 

144 

145 def _assert_model_init(self) -> None: 

146 super()._assert_model_init() 

147 assert self.pipeline is not None 

148 

149 def _assert_args( 

150 self, 

151 height: int, 

152 width: int, 

153 ) -> None: 

154 """ 

155 Check if the image size is supported for the current parallelism setting. 

156 """ 

157 # CogView4 has specific size requirements 

158 if height % 64 != 0 or width % 64 != 0: 

159 raise ValueError(f"Height and width must be divisible by 64, got {height}x{width}") 

160 

161 @inference_mode() 

162 async def warmup(self) -> None: 

163 logging.info(f"[{self.rank}] Warmup for CogView4 generation.") 

164 await self.generate( 

165 height=512, 

166 width=512, 

167 prompt="A warmup image to initialize the model.", 

168 sampling_steps=2 

169 ) 

170 

171 @inference_mode() 

172 async def generate( 

173 self, 

174 height: int, 

175 width: int, 

176 prompt: str, 

177 guidance_scale: float = 3.5, 

178 sampling_steps: int = 50, 

179 job_id: Optional[str] = None, 

180 ) -> Image.Image: 

181 """ 

182 Generate an image from a prompt using the CogView4 model. 

183 Args: 

184 height (int): Height of the generated image. 

185 width (int): Width of the generated image. 

186 prompt (str): Text prompt to guide the image generation. 

187 guidance_scale (float, optional): Guidance scale for generation. Default is 3.5. 

188 sampling_steps (int, optional): Number of inference steps for sampling. Default is 50. 

189 """ 

190 gen_timer = self._new_gen_timer(job_id) 

191 

192 self._assert_model_init() 

193 self._assert_args(height, width) 

194 assert self.pipeline is not None 

195 

196 self.running = True # Mark running to avoid concurrent calls 

197 

198 try: 

199 seed = self.base_seed if self.base_seed >= 0 else random.randint(0, sys.maxsize) 

200 seed_g = torch.Generator(device=self.device) 

201 seed_g.manual_seed(seed) 

202 

203 def callback_gen_timer( 

204 pipeline: CogView4Pipeline, 

205 step: int, 

206 timestep: int, 

207 callback_kwargs: dict 

208 ) -> dict: 

209 gen_timer.end(f"step_{step:03d}") 

210 if step < sampling_steps - 1: 

211 gen_timer.start(f"step_{step + 1:03d}") 

212 return callback_kwargs 

213 

214 gen_timer.start(f"step_{0:03d}") 

215 output = self.pipeline( # type: ignore[operator] 

216 prompt=prompt, 

217 height=height, 

218 width=width, 

219 guidance_scale=guidance_scale, 

220 num_images_per_prompt=1, 

221 num_inference_steps=sampling_steps, 

222 output_type="pil", 

223 generator=seed_g, 

224 callback_on_step_end=callback_gen_timer, 

225 ) 

226 images = output.images 

227 

228 assert len(images) == 1, f"Expected 1 image, but got {len(images)} images." 

229 

230 return images[0] 

231 finally: 

232 self.running = False 

233 gen_timer.end("total") 

234 

235 def get_health(self) -> Dict[str, Any]: 

236 ret = super().get_health() 

237 ret.update({ 

238 "gpu": self.GPU, 

239 "rank": self.rank, 

240 "world_size": self.world_size, 

241 "torch_compile": self.torch_compile, 

242 "dtype": str(self.param_dtype), 

243 "device_map": getattr(self.pipeline, 'hf_device_map', None) if self.pipeline else None, 

244 }) 

245 return ret 

246 

247 async def get_rest_args( 

248 self, 

249 data_json: Dict[str, Union[str, int, float]] 

250 ) -> Dict[str, Any]: 

251 if data_json is None or not isinstance(data_json, dict): 

252 raise ValueError("Missing JSON body") 

253 

254 prompt = data_json.get("prompt", None) 

255 if prompt is None: 

256 raise ValueError("Missing 'prompt' parameter") 

257 

258 height = int(data_json.get("height", 1024)) 

259 width = int(data_json.get("width", 1024)) 

260 guidance_scale = float(data_json.get("guidance_scale", 3.5)) 

261 steps = int(data_json.get("sampling_steps", 50)) 

262 return { 

263 "task": self.model_name, 

264 "args": { 

265 "prompt": prompt, 

266 "height": height, 

267 "width": width, 

268 "guidance_scale": guidance_scale, 

269 "sampling_steps": steps, 

270 } 

271 }